/* ============================================================
   0xmrerror.me — layout: rail, nav, hero, sections, footer
   ============================================================ */

/* ══ left rail (desktop) ═════════════════════════════════ */
.rail {
  position: fixed;
  inset: 0 auto 0 0;
  width: var(--rail-w);
  z-index: var(--z-nav);
  display: none;
  flex-direction: column;
  gap: var(--sp-6);
  padding: var(--sp-6) var(--sp-5);
  background: var(--bg-1);
  border-right: 1px solid var(--line);
}

.rail-logo {
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--accent-text);
  letter-spacing: -.02em;
}
.rail-logo small {
  display: block;
  color: var(--text-faint);
  font-weight: 400;
  font-size: var(--fs-3xs);
  margin-top: 2px;
}

.rail-nav { display: flex; flex-direction: column; gap: 1px; flex: 1; }
.rail-link {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--r-sm);
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  color: var(--text-dim);
  border-left: 2px solid transparent;
  transition: color var(--t-fast) var(--ease),
              background var(--t-fast) var(--ease),
              border-color var(--t-fast) var(--ease);
}
.rail-link::before { content: './'; color: var(--text-faint); }
.rail-link:hover { color: var(--text-hi); background: var(--bg-2); text-decoration: none; }
/* --text-hi, not --accent-text: the tint is a surface, and --accent-tl is
   only solved for 4.5:1 against the page. Green accent text on its own 10%
   tint over the light page measured 4.27:1 (phosphor 4.35:1). This is the
   same pairing .palette-item[aria-selected] uses -- tint plus --text-hi --
   with the accent carried by the 2px rail on the left instead. */
.rail-link[aria-current="true"] {
  color: var(--text-hi);
  border-left-color: var(--accent);
  background: var(--accent-soft);
}

.rail-progress {
  height: 2px;
  background: var(--line);
  border-radius: var(--r-pill);
  overflow: hidden;
}
.rail-progress i {
  display: block;
  height: 100%;
  width: 0;
  background: var(--accent);
  box-shadow: 0 0 8px var(--accent-glow);
  transition: width .1s linear;
}

.rail-meta {
  font-family: var(--font-mono);
  font-size: var(--fs-3xs);
  color: var(--text-faint);
  line-height: 1.9;
}
.rail-meta a { color: var(--text-dim); }
.rail-meta a:hover { color: var(--accent-text); }

@media (min-width: 1180px) {
  body { padding-left: var(--rail-w); }
  #nav { left: var(--rail-w); }
  .rail { display: flex; }
}

/* ══ top nav ═════════════════════════════════════════════ */
#nav {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: var(--z-nav);
  height: var(--nav-h);
  background: color-mix(in srgb, var(--bg-0) 82%, transparent);
  backdrop-filter: blur(14px) saturate(1.4);
  -webkit-backdrop-filter: blur(14px) saturate(1.4);
  border-bottom: 1px solid transparent;
  transition: border-color var(--t) var(--ease), background var(--t) var(--ease);
}
#nav.is-stuck { border-bottom-color: var(--line); }

.nav-inner {
  height: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
  display: flex;
  align-items: center;
  gap: var(--sp-5);
}

.nav-logo {
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--accent-text);
  margin-right: auto;
}
.nav-logo span { color: var(--text-faint); font-weight: 400; }
.nav-logo:hover { text-decoration: none; color: var(--accent); }

.nav-links { display: flex; gap: var(--sp-5); list-style: none; }
.nav-links a {
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  color: var(--text-dim);
  position: relative;
}
.nav-links a::before { content: './'; color: var(--text-faint); margin-right: 1px; }
.nav-links a:hover { color: var(--text-hi); text-decoration: none; }
.nav-links a[aria-current="true"] { color: var(--accent-text); }

.nav-actions { display: flex; align-items: center; gap: var(--sp-2); }

.nav-toggle {
  display: none;
  flex-direction: column;
  gap: 4px;
  background: none;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: 7px 6px;
  cursor: pointer;
}
.nav-toggle span {
  display: block;
  width: 18px; height: 2px;
  background: var(--text-dim);
  transition: transform var(--t) var(--ease), opacity var(--t) var(--ease);
}
.nav-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

/* ── mobile nav panel ───────────────────────────────────── */
@media (max-width: 900px) {
  .nav-toggle { display: flex; }
  .nav-links {
    position: fixed;
    inset: var(--nav-h) 0 auto 0;
    flex-direction: column;
    gap: 0;
    background: var(--bg-1);
    border-bottom: 1px solid var(--line);
    padding: var(--sp-3) 0 var(--sp-5);
    box-shadow: var(--sh-3);
    display: none;
  }
  .nav-links.is-open { display: flex; }
  .nav-links a { display: block; padding: var(--sp-3) var(--gutter); font-size: var(--fs-sm); }
  .nav-links a[aria-current="true"] { background: var(--accent-soft); color: var(--text-hi); }
  .nav-cv, .nav-kbd { display: none; }
}

/* ══ sections ════════════════════════════════════════════ */
.section { padding-block: var(--section-y); }
.section + .section { border-top: 1px solid var(--line-soft); }
.section--tight { padding-block: clamp(56px, 6vw, 84px); }
/* a page that opens with no hero needs to clear the fixed nav itself */
.section--after-nav { padding-top: calc(var(--nav-h) + var(--sp-10)); }

/* the `cat file` motif that introduces each section.
   Wraps rather than overflows: at 320px the prompt prefix alone is
   ~206px, so a nowrap flex row pushed the command off the page.
   The prefix is one .p-pre so it drops to a new line intact instead
   of shattering into `0xmrerror` / `@` / `terminal` on three lines. */
.cmd-line {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 var(--sp-2);
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  color: var(--text-faint);
  margin-bottom: var(--sp-5);
}
.cmd-line .p-pre  { white-space: nowrap; }
.cmd-line .p-user { color: var(--accent-text); }
.cmd-line .p-host { color: var(--text-faint); }
.cmd-line .p-path { color: var(--accent-2-text); }
.cmd-line .p-sym  { color: var(--text-faint); }
/* a single unbroken token (`open /etc/contact.conf` on a 280px line)
   still has to be breakable, or it overflows on its own */
.cmd-line .p-cmd  { color: var(--text-dim); min-width: 0; overflow-wrap: anywhere; }
.cmd-line .p-out  { color: var(--text-faint); margin-left: auto; }

/* ── typing intro ───────────────────────────────────────────
   js/main.js reveals the command one character at a time when
   the line first scrolls into view. The clip is only on while
   typing: `white-space: nowrap` here overrides the `overflow-wrap:
   anywhere` above, which is load-bearing on a 280px line, so the
   is-typed class (added the instant typing finishes) takes it
   all away again and the command is ordinary wrapping text.
   Without JS none of this applies and the line is simply there. */
.cmd-line .p-cmd.is-typing {
  display: inline-block;
  max-width: 0;
  overflow: hidden;
  white-space: nowrap;
  vertical-align: bottom;
}
.cmd-line .p-cmd.is-typing::after {
  content: '';
  display: inline-block;
  width: .5em;
  height: 1em;
  margin-left: 1px;
  background: var(--accent);
  vertical-align: -.12em;
  animation: blink 1.1s steps(2, start) infinite;
}

.section-head { margin-bottom: var(--sp-10); }
.section-head h2 {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  /* 22px sat barely 1.4x above the 16px body, so a section heading read as
     one more line of text rather than the landmark for everything under it.
     The hero headline runs 38-62px; this is the step between that and the
     body, and it is what separates one band of the page from the next. */
  font-size: clamp(1.5rem, 2.4vw, 1.875rem);
  font-weight: 600;
  letter-spacing: -.02em;
}
.section-head h2::before { content: '>'; color: var(--accent-text); }
.section-head p { margin-top: var(--sp-3); color: var(--text-dim); font-size: var(--fs-md); }
.cursor-block {
  display: inline-block;
  width: .5em; height: 1em;
  background: var(--accent);
  vertical-align: -.12em;
  animation: blink 1.1s steps(2, start) infinite;
}
@keyframes blink { 50% { opacity: 0; } }

/* ══ hero ════════════════════════════════════════════════ */
.hero {
  padding-block: calc(var(--nav-h) + clamp(56px, 8vw, 104px)) clamp(48px, 6vw, 80px);
  position: relative;
  overflow: hidden;
}

/* faint grid + accent bloom, sits behind everything */
.hero::before {
  content: '';
  position: absolute;
  inset: -20% -10% auto -10%;
  height: 120%;
  background:
    radial-gradient(48% 42% at 22% 18%, var(--accent-soft), transparent 70%),
    radial-gradient(38% 36% at 82% 8%, var(--accent-2-soft), transparent 70%),
    linear-gradient(var(--line-soft) 1px, transparent 1px) 0 0 / 100% 46px,
    linear-gradient(90deg, var(--line-soft) 1px, transparent 1px) 0 0 / 46px 100%;
  mask-image: linear-gradient(#000 0%, #000 45%, transparent 92%);
  -webkit-mask-image: linear-gradient(#000 0%, #000 45%, transparent 92%);
  pointer-events: none;
}
.hero > .container { position: relative; }

/* ── aurora ────────────────────────────────────────────────
   Two oversized accent blooms drifting behind the hero. The
   gradient *is* the blur -- no filter -- so each blob is one
   paint and the loop is transform-only: the compositor never
   repaints them. Strength is --aurora-alpha, which the light
   theme turns down rather than inheriting a value solved for a
   dark page. Clipped by .hero's own overflow, so nothing here
   can reach the document's scroll width. */
.hero-aurora {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}
.hero-aurora::before,
.hero-aurora::after {
  content: '';
  position: absolute;
  width: 58vmax;
  height: 58vmax;
  border-radius: 50%;
}
.hero-aurora::before {
  top: -28vmax;
  left: -14vmax;
  background: radial-gradient(circle, var(--accent-soft), transparent 65%);
  opacity: var(--aurora-alpha);
  animation: aurora-drift 26s var(--ease) infinite alternate;
}
.hero-aurora::after {
  top: -20vmax;
  right: -18vmax;
  background: radial-gradient(circle, var(--accent-2-soft), transparent 65%);
  opacity: var(--aurora-alpha);
  animation: aurora-drift 34s var(--ease) infinite alternate-reverse;
}
@keyframes aurora-drift {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(7vmax, 5vmax, 0) scale(1.14); }
}
@media (prefers-reduced-motion: reduce) {
  .hero-aurora::before, .hero-aurora::after { animation: none; }
}

/* ══ hero ════════════════════════════════════════════════
   Editorial rather than terminal. The headline is Inter at display size and
   mono is demoted to a garnish here, because every section below already
   speaks mono and a hero that shouts in the same voice as the rest of the page
   leaves nothing to build towards.

   The block answers three questions in order -- who/where, what, prove it: the
   eyebrow, then headline + lede + CTA, then the stat strip closing it out.
   The pipeline card takes the right column so the widest band of the page is
   not half empty grid, which is what it was: at a 1080px container an 18ch
   headline is ~610px, so the right ~470px carried nothing. */
.hero-eyebrow {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  font-size: var(--fs-2xs);
  font-weight: 600;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: var(--sp-6);
}
.hero-eyebrow::after { content: ''; flex: 1; height: 1px; background: var(--line-soft); }
.hero-eyebrow .sep { color: var(--line-strong); }

.hero h1 {
  font-size: var(--fs-5xl);
  line-height: 1.05;
  letter-spacing: -.035em;
  max-width: 22ch;
}
.hero h1 em { font-style: normal; color: var(--accent-text); }

.hero-split {
  display: grid;
  grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
  gap: clamp(var(--sp-8), 4vw, var(--sp-12));
  align-items: start;
  margin-top: var(--sp-8);
}

.hero-lede {
  font-size: var(--fs-xl);
  line-height: 1.62;
  color: var(--text);
  max-width: 46ch;
}

/* One primary action; the other three are inline links. Four competing
   buttons is why the old hero had no clear next step. */
.hero-cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3) var(--sp-5);
  margin-top: var(--sp-8);
}
.hero-cta .lnk {
  font-family: inherit;
  font-size: var(--fs-sm);
  color: var(--text-dim);
  background: none;
  border: 0;
  border-bottom: 1px solid transparent;
  padding: 2px 0;
  cursor: pointer;
  text-decoration: none;
}
.hero-cta .lnk:hover { color: var(--accent-text); border-color: var(--accent-line); }
.hero-cta .lnk kbd {
  font-family: var(--font-mono);
  font-size: var(--fs-3xs);
  color: var(--text-faint);
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: 1px 4px;
}

.hero-avail {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  margin-top: var(--sp-6);
  font-size: var(--fs-sm);
  color: var(--text-dim);
}
.hero-avail .dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--ok);
  animation: pulse 2.4s var(--ease) infinite;
}

/* ── entrance ──────────────────────────────────────────────
   Runs on load, CSS-only: no JS-gated hidden state, so with no
   JS at all these play exactly the same. Under reduced motion
   they never run and the hero is simply there. The stat strip
   is left to the .reveal system -- it arrives with the scroll,
   not with the page. */
@media (prefers-reduced-motion: no-preference) {
  .hero-eyebrow { animation: hero-rise .55s var(--ease) both .05s; }
  .hero h1      { animation: hero-rise .6s  var(--ease) both .12s; }
  .hero-split   { animation: hero-rise .6s  var(--ease) both .2s; }
}
@keyframes hero-rise {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: none; }
}

/* ── the pipeline card ───────────────────────────────────
   The same five phases the projects section expands on. Here they
   are only names and stage counts, so this stays a table of
   contents rather than competing with the diagram below. */
.hero-phases {
  background: var(--bg-1);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: var(--sp-5) var(--sp-5) var(--sp-4);
  box-shadow: var(--sh-2);
}
.hero-phases-title {
  font-family: var(--font-mono);
  font-size: var(--fs-3xs);
  text-transform: uppercase;
  letter-spacing: .1em;
  color: var(--text-faint);
  padding-bottom: var(--sp-4);
  border-bottom: 1px solid var(--line-soft);
}
.hero-phases ol { list-style: none; position: relative; }
.hero-phases ol::before {
  content: '';
  position: absolute;
  left: 13px; top: 18px; bottom: 18px;
  width: 1px;
  background: linear-gradient(var(--accent-line), var(--line));
}
.hero-phases li {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: 9px 0;
}
.hero-phases li i {
  position: relative;
  z-index: 1;
  flex: none;
  width: 27px; height: 27px;
  display: grid;
  place-items: center;
  font-family: var(--font-mono);
  font-size: var(--fs-3xs);
  font-style: normal;
  color: var(--accent-text);
  background: var(--bg-3);
  border: 1px solid var(--line);
  border-radius: var(--r);
}
.hero-phases li b { font-size: var(--fs-sm); font-weight: 600; color: var(--text-hi); }
.hero-phases li span {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: var(--fs-3xs);
  color: var(--text-faint);
  white-space: nowrap;
}

/* The card needs ~325px before "Clustering & attribution" and its stage count
   stop fighting for the row, and the split gives it 40% of the container. Below
   ~935px viewport width the stack is the honest layout. */
@media (max-width: 940px) {
  .hero-split { grid-template-columns: 1fr; gap: var(--sp-8); }
  .hero h1 { max-width: none; }
}

/* Under ~420px of container the eyebrow's two runs of text and the CTA's four
   items both run out of room. In a flex row every contiguous text run is its
   own anonymous item, so "Tunisia · ICT engineering student @ ENIT" split into
   two ragged columns that each wrapped mid-phrase, and "search K" was stranded
   on a line of its own. Stacked, the sentence wraps as prose and the primary
   action gets the full width. */
@media (max-width: 520px) {
  .hero-eyebrow { display: block; }
  .hero-eyebrow::after { display: none; }
  .hero-cta { gap: var(--sp-4) var(--sp-5); }
  .hero-cta .btn { flex: 1 0 100%; justify-content: center; }
}

@keyframes pulse {
  0%   { box-shadow: 0 0 0 0 rgb(63 185 80 / .45); }
  70%  { box-shadow: 0 0 0 7px rgb(63 185 80 / 0); }
  100% { box-shadow: 0 0 0 0 rgb(63 185 80 / 0); }
}

/* ══ grids ═══════════════════════════════════════════════ */
.grid { display: grid; gap: var(--sp-4); }
/* the min() clamp is load-bearing: a bare `minmax(340px, 1fr)` track keeps
   its 340px floor inside a 280px container, so the whole grid overflows
   the page at 320px. min(340px, 100%) lets the track shrink with the
   container and still collapse to one column. */
.grid--2 { grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr)); }
.grid--3 { grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr)); }
.grid--skills { grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr)); }
/* Two columns on laptop width and up, one on phones. Rows size to their own
   content -- no grid-auto-rows: 1fr, which forced every card to the tallest
   card's height and left the two image-less cards with a large empty
   interior that read as a pale rectangle at the bottom of the section. */
.grid--projects { grid-template-columns: repeat(auto-fit, minmax(min(380px, 100%), 1fr)); }

/* ══ footer ══════════════════════════════════════════════ */
.site-footer {
  position: relative;
  border-top: 1px solid var(--line);
  padding-block: var(--sp-10) var(--sp-12);
  background: var(--bg-1);
}
/* a hairline of accent running the width of the page, fading out
   at both ends -- the page closes on the brand rather than on a
   grey rule. Sits over the footer's own border, not instead of it. */
.site-footer::before {
  content: '';
  position: absolute;
  top: -1px;
  left: 0;
  right: 0;
  height: 1px;
  background: linear-gradient(90deg,
    transparent, var(--accent-line) 30%, var(--accent-2-soft) 70%, transparent);
  pointer-events: none;
}
.footer-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-8);
}
.footer-prompt {
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  color: var(--text-dim);
}
.footer-prompt b { color: var(--accent-text); font-weight: 500; }
.footer-note { font-size: var(--fs-2xs); color: var(--text-faint); margin-top: var(--sp-2); max-width: 40ch; }
.footer-cols { display: flex; flex-wrap: wrap; gap: var(--sp-10); }
.footer-col h4 {
  font-family: var(--font-mono);
  font-size: var(--fs-3xs);
  text-transform: uppercase;
  letter-spacing: .1em;
  color: var(--text-faint);
  font-weight: 500;
  margin-bottom: var(--sp-3);
}
.footer-col ul { list-style: none; padding: 0; }
.footer-col li { margin-block: 6px; }
.footer-col a { font-size: var(--fs-sm); color: var(--text-dim); }
.footer-col a:hover { color: var(--accent-text); }
.footer-base {
  margin-top: var(--sp-10);
  padding-top: var(--sp-6);
  border-top: 1px solid var(--line-soft);
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4);
  justify-content: space-between;
  font-family: var(--font-mono);
  font-size: var(--fs-3xs);
  color: var(--text-faint);
}

/* ── layout utilities ────────────────────────────────────────────────────
   These existed as inline style="" attributes in the templates, which meant
   the print stylesheet could not reach them and every tweak meant editing
   HTML.  Same declarations, one place.                                  */

.grid--top      { align-items: start; }
.stack-inline   { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.stack-inline--3{ display: flex; flex-wrap: wrap; gap: var(--sp-3); }
.stack-inline--3--up { margin-top: var(--sp-8); }
.margin-top-5   { margin-top: var(--sp-5); }
.margin-top-6   { margin-top: var(--sp-6); }
.nudge-top-0    { margin-top: 0; }
