/* ──────────────────────────────────────────────────────────────────────────
   The room is the page. Everything here is either the canvas, something
   floating over it, or the plain-text fallback for when the canvas cannot run.
   ────────────────────────────────────────────────────────────────────────── */

:root {
  /* Pulled from the room itself: the lamp, the monitor, the brick, the paper
     on the desk. Nothing here is a generic UI colour. */
  --warm:      #c8862a;
  --warm-deep: #9a5f16;
  --teal:      #3f8e8c;
  --bg:        #241d19;

  --paper:     #fbf6ec;
  --paper-2:   #efe4cf;
  --paper-edge: #e2d4bb;
  --ink:       #2e2823;
  --ink-body:  #423a30;
  --muted:     #857762;

  /* The editor the monitor opens into. */
  --screen-bg:   #12161d;
  --screen-bar:  #1a1f28;
  --screen-ink:  #c6d2e2;
  --screen-dim:  #6f7f95;
  --screen-cyan: #7fd0c4;

  --font:    ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --display: "Fraunces", Georgia, "Times New Roman", serif;
  --mono:    "JetBrains Mono", ui-monospace, "Cascadia Mono", Menlo, monospace;

  /* Warm and brown rather than black: the room is lit from the left by a low
     sun, and a neutral grey shadow under a card reads as a sticker on glass. */
  --lift: 0 2px 6px rgba(58, 36, 14, .16),
          0 18px 40px -14px rgba(58, 36, 14, .46),
          0 60px 90px -50px rgba(58, 36, 14, .5);
}

* { margin: 0; padding: 0; box-sizing: border-box; }

html, body { height: 100%; overflow: hidden; background: var(--bg); }
body { font-family: var(--font); color: var(--ink); -webkit-font-smoothing: antialiased; }

#app { position: fixed; inset: 0; }
/* The renderer sizes the canvas buffer for the device pixel ratio and leaves
   the CSS size to us, so the backing store can be 2x the layout size. Without
   these two lines the canvas lays out at its buffer size and the room is drawn
   at double scale, cropped to the top-left quarter of the viewport. */
#app canvas { display: block; width: 100%; height: 100%; touch-action: none; }

/* ── Crawlable text mirror ───────────────────────────────────────────────
   Off-screen rather than display:none, so it stays in the accessibility tree
   and in the HTML that crawlers read. Becomes the visible page if the room
   fails to start. */
.crawl {
  position: absolute; width: 1px; height: 1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}
body.room-failed { overflow: auto; background: var(--bg); }
body.room-failed #app, body.room-failed #tooltip { display: none; }
body.room-failed .crawl {
  position: static; width: auto; height: auto;
  clip-path: none; white-space: normal;
  max-width: 46rem; margin: 0 auto; padding: 3rem 1.4rem 6rem;
  color: #d8cfc0; line-height: 1.65;
}
body.room-failed .crawl h1 { font-size: 1.9rem; margin-bottom: .3rem; color: #f3ece0; }
body.room-failed .crawl h2 { font-size: 1.15rem; margin: 1.8rem 0 .4rem; color: #f3ece0; }
body.room-failed .crawl h3 { font-size: 1rem; margin: 1.1rem 0 .3rem; color: #e8dfd0; }
body.room-failed .crawl p, body.room-failed .crawl li { margin-bottom: .7rem; }
body.room-failed .crawl ul { padding-left: 1.1rem; }
body.room-failed .crawl a { color: #8fd0cd; }

/* ── Loading ─────────────────────────────────────────────────────────────── */
#loading {
  position: fixed; inset: 0; z-index: 60;
  display: grid; place-items: center;
  background: var(--bg); color: #cbbfae;
  transition: opacity .5s ease;
}
#loading.done { opacity: 0; pointer-events: none; }
.loader-inner { text-align: center; font-size: .9rem; letter-spacing: .04em; }
.spinner {
  width: 30px; height: 30px; margin: 0 auto 1rem;
  border: 2px solid rgba(217, 154, 43, .25);
  border-top-color: var(--warm);
  border-radius: 50%;
  animation: spin 900ms linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ── Tooltip ─────────────────────────────────────────────────────────────
   A label pinned to an object, not a browser tooltip: mono, letterspaced, with
   a warm rule under it that picks up the lamp. */
#tooltip {
  position: fixed; z-index: 40; pointer-events: none;
  padding: .42rem .72rem .38rem;
  background: rgba(28, 22, 17, .92);
  border-bottom: 2px solid var(--warm);
  border-radius: 4px;
  color: #f6eee0;
  font-family: var(--mono); font-size: .72rem; font-weight: 500;
  letter-spacing: .07em; text-transform: uppercase; white-space: nowrap;
  box-shadow: 0 8px 24px rgba(20, 12, 4, .4);
  opacity: 0; transform: translateY(5px) scale(.97);
  transition: opacity .16s ease, transform .16s cubic-bezier(.2, .9, .3, 1);
}
#tooltip.show { opacity: 1; transform: translateY(0) scale(1); }

/* ── Index ───────────────────────────────────────────────────────────────
   Hidden where a mouse can hover, because there the room teaches itself. It
   appears on touch and on any screen too narrow to explore comfortably - the
   two cases where "just poke around" stops being an answer. */
#index-btn, #index-menu, #index-backdrop, #hint { display: none; }

@media (hover: none), (max-width: 720px) {
  #index-btn {
    display: flex; position: fixed; z-index: 48;
    right: 1rem; bottom: 1rem;
    width: 52px; height: 52px; border: 0; border-radius: 50%;
    flex-direction: column; align-items: center; justify-content: center; gap: 4px;
    background: rgba(28, 22, 17, .9);
    box-shadow: 0 4px 18px rgba(20, 12, 4, .5), 0 0 0 1px rgba(255, 240, 210, .12);
    cursor: pointer; backdrop-filter: blur(8px);
    transition: transform .22s cubic-bezier(.2, .9, .3, 1);
  }
  #index-btn i {
    width: 18px; height: 2px; border-radius: 1px; background: #f3e6cf;
    transition: transform .24s cubic-bezier(.2, .9, .3, 1), opacity .18s;
  }
  /* Becomes an X while the list is open, so one button does both jobs. */
  body.index-open #index-btn i:nth-child(1) { transform: translateY(6px) rotate(45deg); }
  body.index-open #index-btn i:nth-child(2) { opacity: 0; }
  body.index-open #index-btn i:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

  #index-backdrop {
    display: block; position: fixed; inset: 0; z-index: 46;
    background: rgba(20, 14, 8, .55);
    opacity: 0; pointer-events: none; transition: opacity .28s ease;
  }
  body.index-open #index-backdrop { opacity: 1; pointer-events: auto; }

  #index-menu {
    display: block; position: fixed; z-index: 47;
    left: .8rem; right: .8rem; bottom: 4.6rem;
    max-height: 62vh; overflow-y: auto; overscroll-behavior: contain;
    padding: .45rem;
    border-radius: 12px;
    background: linear-gradient(158deg, var(--paper), var(--paper-2));
    box-shadow: var(--lift);
    opacity: 0; transform: translateY(14px) scale(.98);
    pointer-events: none;
    transition: opacity .26s ease, transform .3s cubic-bezier(.16, .9, .3, 1);
  }
  body.index-open #index-menu {
    opacity: 1; transform: none; pointer-events: auto;
  }

  .index-item {
    display: flex; align-items: baseline; gap: .6rem; width: 100%;
    /* 48px of height, because this list exists precisely because the room's
       own targets were too small for a thumb. */
    min-height: 48px; padding: .7rem .8rem;
    border: 0; border-radius: 8px; background: transparent;
    text-align: left; cursor: pointer; color: var(--ink);
    font-family: var(--font); font-size: .95rem;
  }
  .index-item + .index-item { border-top: 1px solid rgba(120, 100, 70, .16); border-radius: 0; }
  .index-item:active { background: rgba(200, 134, 42, .16); }
  .index-item em {
    margin-left: auto; flex: none; font-style: normal;
    font-family: var(--mono); font-size: .58rem; letter-spacing: .12em;
    text-transform: uppercase; color: var(--muted);
  }

  #hint {
    display: block; position: fixed; z-index: 45;
    left: 50%; bottom: 1.6rem; transform: translateX(-50%);
    padding: .5rem .85rem; border-radius: 20px;
    background: rgba(28, 22, 17, .88); color: #f4ecdd;
    font-family: var(--mono); font-size: .62rem;
    letter-spacing: .1em; text-transform: uppercase; white-space: nowrap;
    box-shadow: 0 6px 20px rgba(20, 12, 4, .45);
    animation: hint-in .5s ease both;
    pointer-events: none;
  }
  @keyframes hint-in { from { opacity: 0; transform: translateX(-50%) translateY(8px); } }
  /* Out of the way of the index button. */
  #hint { margin-right: 3.5rem; }
}

/* ── Backdrop ────────────────────────────────────────────────────────────── */
#backdrop {
  position: fixed; inset: 0; z-index: 20;
  background: rgba(20, 16, 13, 0);
  opacity: 0; pointer-events: none;
  transition: opacity .45s ease, background .45s ease;
}
#backdrop.show { opacity: 1; pointer-events: auto; }
#backdrop.dim { background: rgba(20, 16, 13, .38); }

/* ── Panel ───────────────────────────────────────────────────────────────
   A sheet of paper set down on the desk, not a dialog box. Squared-off corners,
   a warm edge, a torn-ribbon accent down the left, and it arrives with a slight
   rotation that settles - the tell that something was placed rather than
   popped up. */
#panel {
  position: fixed; z-index: 30;
  right: 3.4vw; top: 50%;
  transform: translateY(-50%) translate(22px, 10px) rotate(1.1deg);
  width: min(452px, 90vw); max-height: 84vh;
  display: flex; flex-direction: column;
  color: var(--ink);
  border: 1px solid var(--paper-edge);
  border-radius: 5px;
  /* Fine grain over the wash, so the paper is not a flat fill. */
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='.05'/%3E%3C/svg%3E"),
    linear-gradient(158deg, var(--paper) 0%, #f5eeddff 55%, var(--paper-2) 100%);
  box-shadow: var(--lift);
  opacity: 0; pointer-events: none;
  transition: opacity .34s ease, transform .55s cubic-bezier(.16, .9, .3, 1);
}
#panel[aria-hidden="false"] {
  opacity: 1; pointer-events: auto;
  transform: translateY(-50%) translate(0, 0) rotate(0deg);
}

/* More text below the fold. main.js sets data-more from a real measurement of
   the scroll box, so this appears only when there genuinely is more and clears
   the moment you reach the end. A sheet cropped mid-sentence with no fade under
   it is the reason nobody realises it scrolls at all.

   Its own element rather than a pseudo: ::before is the lamp strip and ::after
   is the monitor's glass, and quietly stealing one of those would have broken
   the other in a way nothing would have flagged. */
#panel-fade {
  position: absolute; left: 1px; right: 1px; bottom: 1px; z-index: 4;
  height: 3rem; border-radius: 0 0 4px 4px;
  pointer-events: none; opacity: 0; transition: opacity .25s ease;
  background: linear-gradient(rgba(239, 228, 207, 0), rgba(239, 228, 207, .97));
}
#panel[data-more] #panel-fade { opacity: 1; }
/* Paper cream over a dark editor would read as a smudge on the screen. */
#panel.on-screen #panel-fade {
  background: linear-gradient(rgba(18, 22, 29, 0), rgba(18, 22, 29, .95));
}

/* The lamp, spilling down the edge of the page. */
#panel::before {
  content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  border-radius: 5px 0 0 5px;
  background: linear-gradient(180deg, var(--warm), var(--warm-deep) 70%, #7a4a11);
}

#panel-scroll {
  /* min-height:0 is load-bearing. A flex item defaults to min-height:auto,
     which refuses to shrink below its content - so this box would ignore
     max-height on the panel, grow to fit the text, and push the top of the
     card off the top of the screen instead of scrolling. */
  min-height: 0;
  overflow-y: auto; overscroll-behavior: contain;
  /* Spelled out because the room's canvas sets touch-action: none, and a
     scroller that inherits any doubt about who owns a vertical drag simply
     refuses to move on a phone. */
  touch-action: pan-y;
  -webkit-overflow-scrolling: touch;
  padding: 2.1rem 2.1rem 2.3rem 2.3rem;
  scrollbar-width: thin; scrollbar-color: rgba(120, 100, 70, .3) transparent;
}
#panel-scroll::-webkit-scrollbar { width: 7px; }
#panel-scroll::-webkit-scrollbar-track { background: transparent; }
#panel-scroll::-webkit-scrollbar-thumb {
  background: rgba(120, 100, 70, .28); border-radius: 4px;
}

#panel-tag {
  display: flex; align-items: center; gap: .55rem;
  font-family: var(--mono); font-size: .655rem; font-weight: 500;
  letter-spacing: .2em; text-transform: uppercase; color: var(--muted);
}
#panel-tag::before {
  content: ''; width: 14px; height: 1px; background: var(--warm); flex: none;
}

#panel-title {
  font-family: var(--display); font-weight: 600;
  font-size: clamp(1.5rem, 2.3vw, 1.95rem);
  line-height: 1.1; letter-spacing: -.015em;
  margin: .5rem 0 1.05rem;
  padding-bottom: 1.05rem;
  border-bottom: 1px solid rgba(120, 100, 70, .2);
}

#panel-close {
  position: absolute; top: .8rem; right: .8rem; z-index: 2;
  width: 28px; height: 28px; border: 0; border-radius: 50%;
  background: transparent; color: var(--muted);
  font-size: .78rem; line-height: 1; cursor: pointer;
  transition: background .2s, color .2s, transform .3s cubic-bezier(.2, .9, .3, 1);
}
#panel-close:hover, #panel-close:focus-visible {
  background: rgba(46, 40, 35, .09); color: var(--ink); transform: rotate(90deg);
}

/* ── Panel body copy ──────────────────────────────────────────────────── */
.rich { font-size: .935rem; line-height: 1.72; color: var(--ink-body); }
.rich p + p, .rich p + ul, .rich ul + p { margin-top: .85rem; }
.rich ul { list-style: none; padding: 0; }
.rich li { position: relative; padding-left: 1.15rem; margin-bottom: .62rem; }
/* A small turned square instead of a bullet - it echoes the pixel art without
   trying to imitate it. */
.rich li::before {
  content: ''; position: absolute; left: .1rem; top: .66em;
  width: 5px; height: 5px; background: var(--warm);
  transform: rotate(45deg);
}
.rich li:last-child { margin-bottom: 0; }
.rich a {
  color: var(--warm-deep); text-decoration: none;
  border-bottom: 1px solid rgba(154, 95, 22, .3);
  transition: border-color .2s, color .2s;
}
.rich a:hover { color: var(--warm); border-bottom-color: var(--warm); }
.rich strong { font-weight: 650; color: var(--ink); }
.rich em { font-family: var(--mono); font-size: .82em; font-style: normal; color: var(--muted); }

/* ── The close-up ────────────────────────────────────────────────────────
   Off unless a section asks for it. main.js points it at one region of the
   room artwork; everything here is presentation. */
#panel-figure {
  display: none;
  background-image: url('room.webp');
  background-repeat: no-repeat;
  image-rendering: auto;
}

/* ── Hakkımda ────────────────────────────────────────────────────────────
   The one section that is about a person, so it opens with the person: the
   framed portrait on the wall, cut out of the painting and pinned to the page
   at a slight angle, the way a photo actually sits when you put it down. The
   heading below it is the name, not the section label. */
#panel[data-section="about"] #panel-figure {
  display: block;
  width: 104px;
  margin: 0 0 1.25rem 2px;
  transform: rotate(-1.8deg);
  border: 4px solid #fdfaf3;             /* the white margin of a print */
  outline: 1px solid rgba(120, 100, 70, .35);
  box-shadow: 0 3px 8px rgba(58, 36, 14, .22), 0 14px 26px -12px rgba(58, 36, 14, .5);
}
#panel[data-section="about"] #panel-tag {
  order: 2; font-size: .62rem; letter-spacing: .16em; color: var(--warm-deep);
}
#panel[data-section="about"] #panel-title {
  font-size: clamp(1.75rem, 2.7vw, 2.25rem);
  margin: .1rem 0 .4rem;
  padding-bottom: 0; border-bottom: 0;
}
/* Name first, then the role under it - so the tag has to move after the title,
   which is what the flex column and the orders below are for. */
#panel[data-section="about"] #panel-scroll {
  display: flex; flex-direction: column;
}
/* Turning a scroll container into a column flex container quietly breaks it.
   Flex items shrink by default, so with the panel at its maximum height the
   text gets squeezed, spills out of its own box, and the container concludes
   that everything fitted - no overflow, no scrollbar, and the last paragraph
   simply cut off. Refusing to shrink is what puts the scrolling back. */
#panel[data-section="about"] #panel-scroll > * { flex: none; }
#panel[data-section="about"] #panel-figure { order: 0; }
#panel[data-section="about"] #panel-title  { order: 1; }
#panel[data-section="about"] #panel-body {
  order: 3; margin-top: 1.2rem; padding-top: 1.2rem;
  border-top: 1px solid rgba(120, 100, 70, .2);
}

/* ── Deneyim ─────────────────────────────────────────────────────────────
   A career is a line, not a bulleted list, and the line is the whole story
   here: a PLC bench in 2019 at one end, Laravel and AI in 2025 at the other.
   So this section draws it - a spine down the left with a marker per stop.

   Every rule is scoped under .rich so it beats the generic list styling above
   without depending on which one happens to come later in the file. */
#panel[data-section="experience"] #panel-title { margin-bottom: .85rem; }

.rich .lede {
  font-size: .89rem; color: var(--muted); line-height: 1.6;
  margin-bottom: 1.4rem;
}

.rich .timeline {
  list-style: none; margin: 0; padding: 0; position: relative;
}
/* The spine. Inset top and bottom so it starts and ends at the first and last
   marker rather than floating past them. */
.rich .timeline::before {
  content: ''; position: absolute; left: 4px; top: .55rem; bottom: .6rem;
  width: 1px;
  background: linear-gradient(180deg, var(--warm), rgba(200, 134, 42, .18));
}
.rich .timeline li {
  position: relative; margin: 0; padding: 0 0 1.4rem 1.65rem;
}
.rich .timeline li:last-child { padding-bottom: 0; }

/* The marker: the same turned square the bullets use, so the two read as one
   family. Filled for a job, hollow for an internship. The ring is the paper
   colour, which punches the marker out of the spine behind it. */
.rich .timeline li::before {
  content: ''; position: absolute; left: 0; top: .42rem;
  width: 9px; height: 9px; transform: rotate(45deg);
  background: var(--warm);
  box-shadow: 0 0 0 3.5px var(--paper);
}
.rich .timeline li[data-kind="staj"]::before {
  background: var(--paper);
  box-shadow: inset 0 0 0 1.5px var(--warm), 0 0 0 3.5px var(--paper);
}

.rich .tl-year {
  display: block; font-family: var(--mono); font-size: .655rem; font-weight: 500;
  letter-spacing: .17em; color: var(--warm-deep); margin-bottom: .15rem;
}
.rich .timeline strong {
  display: block; font-size: .97rem; font-weight: 650;
  color: var(--ink); line-height: 1.35;
}
.rich .tl-chip {
  display: inline-block; margin-left: .4rem; vertical-align: 2px;
  padding: .06rem .32rem .04rem;
  font-family: var(--mono); font-style: normal; font-weight: 400;
  font-size: .56rem; letter-spacing: .13em; text-transform: uppercase;
  color: var(--muted);
  border: 1px solid rgba(120, 100, 70, .32); border-radius: 3px;
}
.rich .timeline p { margin-top: .24rem; font-size: .875rem; line-height: 1.6; }

/* ── Yetenekler ──────────────────────────────────────────────────────────
   Opened from the bookshelf, so it is one: a category per shelf, a skill per
   book standing spine-out on it. The board under each row is a real border,
   not a decoration - it is what stops seven rows of chips from reading as one
   undifferentiated cloud of tags. */
#panel[data-section="skills"] #panel-title { margin-bottom: 1.15rem; }

.rich .shelf { margin-bottom: 1.05rem; }
.rich .shelf:last-of-type { margin-bottom: 1.3rem; }
.rich .shelf h3 {
  font-family: var(--mono); font-size: .62rem; font-weight: 500;
  letter-spacing: .18em; text-transform: uppercase; color: var(--muted);
  margin-bottom: .48rem;
}

.rich .spines {
  list-style: none; margin: 0; padding: 0 0 .5rem;
  display: flex; flex-wrap: wrap; gap: .38rem;
  /* The shelf board: a plank edge, with a highlight along its front lip. */
  border-bottom: 2px solid rgba(138, 106, 60, .3);
  box-shadow: 0 1px 0 rgba(255, 255, 255, .65);
}

.rich .spines li {
  position: static; margin: 0;
  padding: .3rem .62rem .3rem .58rem;
  font-size: .845rem; font-weight: 500; color: var(--ink); line-height: 1.35;
  background: linear-gradient(180deg, #fffdf7, #f2ebd9);
  border: 1px solid rgba(120, 100, 70, .26);
  /* The coloured edge is the spine. Asymmetric radius so the bound edge reads
     as the square one and the fore-edge as the rounded one, like a real book. */
  border-left: 4px solid var(--spine);
  border-radius: 2px 4px 4px 2px;
  box-shadow: 0 1px 2px rgba(58, 36, 14, .12);
}
/* The generic diamond bullet has no business inside a shelf. */
.rich .spines li::before { content: none; }

.rich .langs {
  font-size: .85rem; color: var(--muted);
  padding-top: .1rem;
}
.rich .langs strong {
  font-family: var(--mono); font-size: .62rem; font-weight: 500;
  letter-spacing: .18em; text-transform: uppercase; color: var(--muted);
}

/* ── İletişim ────────────────────────────────────────────────────────────
   Opened from the rotary telephone, so each way to reach me is numbered like
   a hole on its dial. Whole rows are the target rather than the text inside
   them - a link you have to aim at is a link people do not follow. */
#panel[data-section="contact"] #panel-title { margin-bottom: 1rem; }

.rich .dial { list-style: none; margin: 0; padding: 0; }
.rich .dial li { position: relative; margin: 0 0 .5rem; padding: 0; }
.rich .dial li:last-child { margin-bottom: 0; }
.rich .dial li::before { content: none; }

.rich .dial a {
  display: flex; align-items: center; gap: .75rem;
  padding: .62rem .8rem .62rem .62rem;
  border: 1px solid rgba(120, 100, 70, .24);
  border-radius: 4px;
  background: linear-gradient(180deg, #fffdf7, #f4eddc);
  color: var(--ink); text-decoration: none;
  box-shadow: 0 1px 2px rgba(58, 36, 14, .08);
  transition: border-color .18s, box-shadow .18s, transform .18s;
}
.rich .dial a:hover, .rich .dial a:focus-visible {
  border-color: var(--warm);
  box-shadow: 0 2px 10px rgba(58, 36, 14, .16);
  transform: translateX(2px);
}

/* The dial hole. Bakelite black against the cream body, like the phone. */
.rich .dial b {
  flex: none; width: 27px; height: 27px; border-radius: 50%;
  display: grid; place-items: center;
  font-family: var(--mono); font-size: .7rem; font-weight: 500;
  color: #f6eee0; background: #2b2521;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .16);
}

.rich .dial-text { min-width: 0; }
.rich .dial .what {
  display: block;
  font-family: var(--mono); font-size: .56rem; font-weight: 500;
  letter-spacing: .17em; text-transform: uppercase; color: var(--muted);
}
.rich .dial .val {
  display: block; font-size: .875rem; line-height: 1.3;
  /* The address is long and the panel is narrow; break it rather than let it
     push the row wider than the card. */
  overflow-wrap: anywhere;
}
.rich .dial .go {
  margin-left: auto; padding-left: .5rem;
  color: var(--muted); font-size: .85rem; transition: color .18s;
}
.rich .dial a:hover .go { color: var(--warm); }

/* Copy button. A mailto: link is a dead end for anyone without a desktop mail
   client, so the address is also grabbable as text. Sits beside the link, not
   inside it - a button nested in an anchor is invalid and swallows its clicks. */
.rich .dial .has-copy a { padding-right: 5.2rem; }
.rich .dial .copy {
  position: absolute; right: .55rem; top: 50%; transform: translateY(-50%);
  padding: .3rem .5rem; border: 1px solid rgba(120, 100, 70, .3);
  border-radius: 3px; background: rgba(255, 255, 255, .6);
  font-family: var(--mono); font-size: .58rem; font-weight: 500;
  letter-spacing: .1em; text-transform: uppercase; color: var(--muted);
  cursor: pointer; transition: background .18s, color .18s, border-color .18s;
}
.rich .dial .copy:hover { background: #fff; color: var(--ink); border-color: var(--warm); }
.rich .dial .copy.copied {
  color: #3f7a4a; border-color: #3f7a4a; background: rgba(63, 122, 74, .1);
}

/* ── Eğitim ──────────────────────────────────────────────────────────────
   Opened from the framed diploma, so each entry is drawn as a document: the
   double rule that every certificate has, and the GPA lifted out into a seal.
   3.35 is the number worth reading in this section; a bulleted list buries it
   halfway through a sentence. */
#panel[data-section="education"] #panel-title { margin-bottom: 1.05rem; }

.rich .cert {
  position: relative;
  margin-bottom: .85rem;
  padding: 1.15rem 1.2rem 1.2rem;
  padding-right: 5.2rem;                 /* the seal lives here */
  background: linear-gradient(165deg, #fffdf8, #f6efdf);
  border: 1px solid rgba(138, 106, 60, .42);
  box-shadow: 0 1px 3px rgba(58, 36, 14, .1);
}
/* The inner rule. A certificate is never bounded by a single line. */
.rich .cert::before {
  content: ''; position: absolute; inset: 4px;
  border: 1px solid rgba(138, 106, 60, .22);
  pointer-events: none;
}
.rich .cert:last-of-type { padding-right: 1.2rem; }

.rich .cert-year {
  display: block; margin-bottom: .3rem;
  font-family: var(--mono); font-size: .6rem; font-weight: 500;
  letter-spacing: .19em; color: var(--warm-deep);
}
.rich .cert h3 {
  font-family: var(--display); font-weight: 600;
  font-size: 1.12rem; line-height: 1.2; color: var(--ink);
  letter-spacing: -.01em;
}
.rich .cert-prog {
  margin-top: .18rem; font-size: .855rem; color: var(--muted);
}

/* The seal. Off-centre highlight so it reads as pressed wax rather than a
   flat circle, and a hairline ring inside the rim. */
.rich .seal {
  position: absolute; top: 1.05rem; right: 1.05rem;
  width: 56px; height: 56px; border-radius: 50%;
  display: grid; place-items: center; align-content: center;
  text-align: center; line-height: 1; color: #fff8e8;
  background: radial-gradient(circle at 34% 28%, #e2ab45, #b5761d 72%);
  box-shadow:
    inset 0 0 0 2px rgba(255, 248, 232, .3),
    0 2px 7px rgba(58, 36, 14, .32);
}
.rich .seal b {
  display: block; font-family: var(--mono);
  font-size: .85rem; font-weight: 500; letter-spacing: -.01em;
}
.rich .seal i {
  display: block; margin-top: .22rem; font-style: normal;
  font-family: var(--mono); font-size: .47rem;
  letter-spacing: .16em; opacity: .82;
}

.rich .note {
  margin-top: 1.15rem; padding-top: 1.05rem;
  border-top: 1px solid rgba(120, 100, 70, .2);
  font-size: .89rem; color: var(--muted); line-height: 1.65;
}

/* ── Rakamlarla ──────────────────────────────────────────────────────────
   Stat tiles, not a chart: four unrelated counts plotted as bars would invite
   comparisons between them that carry no meaning. The number is the chart. */
#panel[data-section="awards"] #panel-title { margin-bottom: 1.05rem; }

.rich .stats {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: 1fr 1fr; gap: .55rem;
}
.rich .stats li {
  position: relative; margin: 0;
  padding: 1rem 1rem 1.05rem;
  background: linear-gradient(165deg, #fffdf8, #f6efdf);
  border: 1px solid rgba(120, 100, 70, .24);
  border-radius: 4px;
  box-shadow: 0 1px 2px rgba(58, 36, 14, .08);
}
/* The accent is a mark on the tile, not the colour of the number: a value
   painted in the accent hue reads as a category, and these are not categories.
   Every property of the generic diamond bullet has to be undone here. */
.rich .stats li::before {
  content: ''; position: absolute; left: 1rem; top: 0;
  width: 22px; height: 2px;
  background: var(--warm); transform: none; border-radius: 0 0 1px 1px;
}

.rich .stat-v {
  display: block;
  /* Sans, never the display face - Fraunces at this size reads as ornament
     rather than data. And no tabular-nums: equal-width digits make a value
     like 3.35 look loose at display sizes. Proportional figures are correct
     until numbers have to line up in a column. */
  font-family: var(--font);
  font-size: clamp(1.55rem, 3.2vw, 2.05rem);
  font-weight: 640; line-height: 1; letter-spacing: -.025em;
  color: var(--ink);
}
.rich .stat-l {
  display: block; margin-top: .45rem;
  font-size: .775rem; line-height: 1.42; color: var(--muted);
}

/* ── Backend & API ───────────────────────────────────────────────────────
   Opened from the server rack, so the stack is drawn as one: a unit per layer,
   each with its own indicator. This is the only block on the site that is not
   paper, which is exactly right for the one section about machines. */
#panel[data-section="backend"] #panel-title { margin-bottom: .85rem; }

.rich .rack {
  list-style: none; margin: 1.15rem 0 0; padding: 5px;
  background: linear-gradient(180deg, #262c33, #191d23);
  border-radius: 5px;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .07),
    0 2px 10px rgba(58, 36, 14, .28);
}
.rich .rack li {
  display: flex; align-items: center; gap: .7rem;
  margin: 0 0 3px; padding: .58rem .72rem;
  background: linear-gradient(180deg, #30363d, #262b31);
  border-radius: 3px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .05);
}
.rich .rack li:last-child { margin-bottom: 0; }
.rich .rack li::before { content: none; }

.rich .rack .led {
  flex: none; width: 7px; height: 7px; border-radius: 50%;
  background: #57d98a; box-shadow: 0 0 6px rgba(87, 217, 138, .75);
  animation: led-pulse 3.4s ease-in-out infinite;
}
/* Staggered so the rack reads as several independent machines rather than one
   thing flashing. Decoration only - nothing here encodes data, so driving it
   off nth-child costs nothing. */
.rich .rack li:nth-child(2) .led { animation-delay: -.7s; }
.rich .rack li:nth-child(3) .led { animation-delay: -1.9s; }
.rich .rack li:nth-child(4) .led { animation-delay: -2.6s; }
.rich .rack li:nth-child(5) .led { animation-delay: -1.2s; }
@keyframes led-pulse {
  0%, 62%, 100% { opacity: 1; }
  70%, 78%      { opacity: .28; }
}

.rich .rack .u {
  font-family: var(--mono); font-size: .625rem; font-weight: 500;
  letter-spacing: .15em; text-transform: uppercase; color: #cbd6e4;
}
.rich .rack .t {
  margin-left: auto; padding-left: .6rem; text-align: right;
  font-family: var(--mono); font-size: .655rem; color: var(--screen-cyan);
}

/* ── Otomasyon & Yapay Zekâ ──────────────────────────────────────────────
   This section has a story, not a structure, so it gets typography instead of
   a widget: a close-up of the workbench you clicked, then the thesis, then the
   two stops that support it. Every other panel here structures real content;
   an ornament invented for this one read as exactly that. */
#panel[data-section="hardware"] #panel-title { margin-bottom: 1.05rem; }

/* Detection boxes over the room. The section is half about image processing,
   so it shows image processing rather than an instrument that belongs to the
   other half. The subject is the room the visitor is already standing in,
   which is what keeps it from being an arbitrary picture. */
.rich .detect {
  position: relative; margin: 0 0 1.25rem;
  border: 1px solid rgba(120, 100, 70, .3); border-radius: 3px;
  box-shadow: 0 1px 3px rgba(58, 36, 14, .16);
  overflow: hidden;
}
.rich .detect-img {
  /* A fixed window onto the desk. Hard-coded rather than driven from the
     hotspot, because the frame has to contain three recognisable objects -
     that is a composition decision, not a hit-testing one. */
  aspect-ratio: 2.25 / 1;
  background-image: url('room.webp');
  background-size: 263% 333%;
  background-position: 48.4% 42.9%;
}
.rich .detect .box {
  position: absolute;
  left: var(--x); top: var(--y); width: var(--w); height: var(--h);
  border: 1.5px solid var(--screen-cyan);
  border-radius: 2px;
  /* A dark hairline outside the cyan keeps the box readable over both the
     bright lamp and the dark monitor. */
  box-shadow: 0 0 0 1px rgba(0, 0, 0, .4);
}
.rich .detect .box i {
  position: absolute; left: -1px; top: -1px;
  padding: .13rem .32rem .11rem;
  background: var(--screen-cyan); color: #0c1a18;
  border-radius: 2px 0 3px 0;
  font-family: var(--mono); font-style: normal; font-size: .53rem;
  font-weight: 500; letter-spacing: .1em; text-transform: uppercase;
  white-space: nowrap;
}
.rich .detect figcaption {
  padding: .45rem .6rem .5rem;
  background: #14181c;
  font-family: var(--mono); font-size: .555rem; font-weight: 500;
  letter-spacing: .13em; text-transform: uppercase; color: #7f9a94;
  text-align: center;
}

/* The one sentence the section exists to say, set in the display face so it
   carries without needing a rule, a box or a colour to hold it up. */
.rich .thesis {
  font-family: var(--display); font-weight: 600;
  font-size: 1.15rem; line-height: 1.35; letter-spacing: -.012em;
  color: var(--ink);
  margin-bottom: 1rem;
}

/* ── Mobil Geliştirme ────────────────────────────────────────────────────
   One codebase reaching two platforms, drawn: a source, a bracket, two
   outputs. Pure borders - no SVG and no image - so it stays crisp at any zoom
   and inherits the paper's own colours. */
#panel[data-section="mobile"] #panel-title { margin-bottom: .9rem; }

.rich .fanout {
  position: relative; text-align: center;
  margin: 1.35rem 0 1.5rem;
}

.rich .fan-src, .rich .fan-outs > span {
  display: inline-block;
  padding: .46rem .8rem;
  font-family: var(--mono); font-size: .7rem; font-weight: 500;
  letter-spacing: .05em; color: var(--ink); line-height: 1;
  background: linear-gradient(180deg, #fffdf7, #f2ebd9);
  border: 1px solid rgba(120, 100, 70, .3);
  border-radius: 4px;
  box-shadow: 0 1px 2px rgba(58, 36, 14, .1);
}
.rich .fan-src { border-left: 3px solid var(--warm); }

.rich .fan-outs {
  position: relative;
  display: flex; justify-content: center; gap: 2.4rem;
  margin-top: 1.5rem;
}
/* The bracket: one rule across the pair with a leg dropping to each. Drawn as
   three sides of a box with the bottom removed, which is cheaper and sharper
   than any graphic and cannot drift out of alignment with the chips. */
.rich .fan-outs::before {
  content: ''; position: absolute;
  left: 21%; right: 21%; top: -1.02rem; height: 1.02rem;
  border: 1px solid rgba(138, 106, 60, .45);
  border-bottom: 0; border-radius: 4px 4px 0 0;
}
/* The stem, from the source down into the bracket. */
.rich .fan-outs::after {
  content: ''; position: absolute; left: 50%; top: -1.5rem;
  width: 1px; height: .48rem; margin-left: -.5px;
  background: rgba(138, 106, 60, .45);
}

/* ── İşin Dışında ────────────────────────────────────────────────────────
   The one section that is not about work, so it is the one that gets to
   breathe: numbered like the contents page of the book on the table, wide
   spacing, hairlines instead of a spine. Deliberately unlike the timeline -
   two numbered vertical lists in the same site have to look different or the
   reader stops telling them apart. */
#panel[data-section="hobbies"] #panel-title { margin-bottom: .9rem; }

.rich .offclock { list-style: none; margin: 1.35rem 0 0; padding: 0; }
.rich .offclock li {
  position: relative; margin: 0 0 1.3rem; padding: 0 0 1.3rem 2.45rem;
  border-bottom: 1px solid rgba(120, 100, 70, .16);
}
.rich .offclock li:last-child {
  margin-bottom: 0; padding-bottom: 0; border-bottom: 0;
}
.rich .offclock li::before { content: none; }

.rich .oc-n {
  position: absolute; left: 0; top: .2rem;
  font-family: var(--mono); font-size: .72rem; font-weight: 500;
  letter-spacing: .05em; color: var(--warm);
}
.rich .offclock h3 {
  font-family: var(--display); font-weight: 600;
  font-size: 1.12rem; line-height: 1.2; letter-spacing: -.012em;
  color: var(--ink);
}
.rich .offclock p {
  margin-top: .32rem; font-size: .885rem; line-height: 1.62;
  color: var(--ink-body);
}

/* ── Ziyaretçi Defteri ───────────────────────────────────────────────────
   The whole viewport, not a panel. A board with a hundred notes on it needs
   the room; the same hundred inside a 450px card stop being a wall and become
   a list. Notes overlap and run off to the right, and you scroll sideways
   through them.

   Every note keeps its place: position, tilt and colour all come from the
   note's own id or stored data, never from Math.random at paint time. A board
   that reshuffles itself each time you open it reads as broken. */
:root {
  --note-0: #f6e79a;   /* the sticky-note colours off the room's own wall */
  --note-1: #b6e0cf;
  --note-2: #f4c1bb;
  --note-3: #cfc7ec;
  --note-4: #f8d4a2;
}

#wall {
  position: fixed; inset: 0; z-index: 46;
  display: flex; flex-direction: column;
  opacity: 0; pointer-events: none;
  transition: opacity .45s ease .25s;
  background-color: #6b4a28;
}
#wall.show { opacity: 1; pointer-events: auto; }

/* The backdrop is the room itself, thrown out of focus. No new asset, and it
   keeps the board somewhere rather than nowhere: you are still in the room,
   you have just walked up to the wall. A flat generated cork texture reads as
   a different place entirely. */
#wall::before {
  content: ''; position: absolute; inset: -60px; z-index: 0;
  background: url('room.webp') center / cover no-repeat;
  filter: blur(34px) saturate(.7) brightness(.42);
}
/* Cork over the top of it: warm wash plus fine speckle. Layered rather than
   replacing the photo, so the board still carries the room's own light. */
#wall::after {
  content: ''; position: absolute; inset: 0; z-index: 0;
  background-image:
    radial-gradient(circle at 20% 30%, rgba(90, 58, 26, .5) 0 1.5px, transparent 2px),
    radial-gradient(circle at 70% 65%, rgba(110, 74, 34, .45) 0 1.8px, transparent 2.4px),
    radial-gradient(circle at 45% 85%, rgba(80, 50, 22, .4) 0 1.2px, transparent 1.8px),
    linear-gradient(150deg, rgba(186, 140, 84, .82), rgba(146, 104, 58, .88));
  background-size: 34px 34px, 47px 47px, 29px 29px, 100% 100%;
  box-shadow: inset 0 0 120px rgba(60, 36, 14, .6);
}
/* Both layers are painted behind everything, so every child needs to be told
   it belongs in front. */
#wall > * { position: relative; z-index: 1; }

/* ── Header ───────────────────────────────────────────────────────────── */
#wall-head {
  flex: none; position: relative; z-index: 2;
  display: flex; align-items: flex-end; gap: 1.5rem;
  padding: 1.5rem 4rem 1.25rem 2rem;
  background: linear-gradient(180deg, rgba(50, 32, 14, .55), rgba(50, 32, 14, 0));
}
.wall-tag {
  font-family: var(--mono); font-size: .6rem; font-weight: 500;
  letter-spacing: .2em; text-transform: uppercase; color: rgba(255, 244, 226, .65);
  margin-bottom: .2rem;
}
#wall-title {
  font-family: var(--display); font-weight: 600;
  font-size: clamp(1.35rem, 2.6vw, 1.95rem); line-height: 1;
  color: #fff8ec; text-shadow: 0 2px 12px rgba(50, 28, 8, .5);
}
#wall-close {
  position: absolute; top: 1.2rem; right: 1.3rem;
  width: 36px; height: 36px; border: 0; border-radius: 50%;
  background: rgba(255, 255, 255, .2); color: #fff;
  font-size: .9rem; cursor: pointer; backdrop-filter: blur(6px);
  transition: background .2s, transform .25s;
}
#wall-close:hover { background: rgba(255, 255, 255, .32); transform: rotate(90deg); }

/* ── The scrolling strip ──────────────────────────────────────────────── */
.gb-scroll {
  flex: 1 1 auto; min-height: 0;
  overflow-x: auto; overflow-y: hidden;
  overscroll-behavior: contain;
  scrollbar-width: auto; scrollbar-color: rgba(90, 58, 24, .75) rgba(255, 255, 255, .18);
}
.gb-scroll::-webkit-scrollbar { height: 14px; }
.gb-scroll::-webkit-scrollbar-track {
  background: rgba(90, 58, 24, .28); border-top: 1px solid rgba(70, 44, 18, .4);
}
.gb-scroll::-webkit-scrollbar-thumb {
  background: rgba(252, 240, 220, .55); border-radius: 7px;
  border: 3px solid transparent; background-clip: padding-box;
}
.gb-scroll::-webkit-scrollbar-thumb:hover { background: rgba(255, 248, 236, .8); background-clip: padding-box; }

/* Width and every note's offset are set from JS - the row count depends on the
   viewport height, which CSS cannot compute into a scroll extent. */
.gb-wall { position: relative; height: 100%; list-style: none; margin: 0; padding: 0; }

.note {
  position: absolute; width: 168px; min-height: 168px;
  margin: 0; padding: 1.15rem .7rem .6rem;
  display: flex; flex-direction: column;
  background: var(--paper);
  transform: rotate(var(--tilt));
  box-shadow: 0 2px 4px rgba(60, 38, 14, .3), 0 10px 18px -10px rgba(60, 38, 14, .5);
  transition: transform .18s cubic-bezier(.2, .9, .3, 1), box-shadow .18s;
}
.note::before { content: none; }
/* Lifting a note on hover is the only way to read one buried under three
   others, so it is a feature rather than a flourish. */
.note:hover {
  transform: rotate(var(--tilt)) scale(1.06);
  box-shadow: 0 4px 10px rgba(60, 38, 14, .35), 0 20px 34px -14px rgba(60, 38, 14, .55);
  z-index: 999 !important;
}
/* The pin. */
.note::after {
  content: ''; position: absolute; left: 50%; top: .4rem;
  width: 10px; height: 10px; margin-left: -5px; border-radius: 50%;
  background: radial-gradient(circle at 32% 30%, #f28b7a, #b23f30 70%);
  box-shadow: 0 1px 3px rgba(50, 20, 10, .55);
}

.note p {
  margin: 0; flex: 1 1 auto;
  font-family: "Caveat", var(--font); font-size: 1.28rem; line-height: 1.22;
  color: #38302a;
  /* Someone will paste a link with no spaces in it. */
  overflow-wrap: anywhere;
}
.note-long p { font-size: 1.02rem; line-height: 1.24; }

.note-by, .note-when {
  font-family: var(--mono); letter-spacing: .05em;
  overflow-wrap: anywhere;
}
.note-by {
  margin-top: .4rem; font-size: .55rem; font-weight: 500;
  color: rgba(56, 48, 42, .78); text-transform: uppercase;
}
.note-when { font-size: .5rem; color: rgba(56, 48, 42, .5); }

/* ── The composer ────────────────────────────────────────────────────────
   You do not fill in a form and get a note back: you pick a colour and write
   on the paper. So the composer is a sticky note - same paper, same pin, same
   tilt - only bigger, with the swatches on its top corner where the choice
   belongs to the sheet in front of you.

   It floats over the board rather than living in the scrolling strip, so it
   can never scroll out of reach. */
.gb-composer {
  position: absolute; left: 2.2rem; bottom: 2.6rem; z-index: 3;
  display: flex; flex-direction: column; align-items: flex-start; gap: .55rem;
}

.gb-note-form {
  position: relative;
  width: 286px; min-height: 286px;
  padding: 2.5rem 1.15rem 1rem;
  display: flex; flex-direction: column;
  background: var(--paper);
  transform: rotate(-1.6deg);
  box-shadow: 0 3px 8px rgba(50, 30, 10, .34), 0 22px 40px -16px rgba(50, 30, 10, .6);
  /* The colour swap should feel like reaching for a different pad, not like a
     UI state change. */
  transition: background .28s ease;
}
/* The same pin every other note has. */
.gb-note-form::after {
  content: ''; position: absolute; left: 50%; top: .55rem;
  width: 11px; height: 11px; margin-left: -5.5px; border-radius: 50%;
  background: radial-gradient(circle at 32% 30%, #f28b7a, #b23f30 70%);
  box-shadow: 0 1px 3px rgba(50, 20, 10, .55);
}

.gb-swatches {
  position: absolute; top: .6rem; right: .7rem;
  display: flex; gap: .3rem;
}
.sw {
  width: 17px; height: 17px; padding: 0; cursor: pointer;
  border: 1px solid rgba(70, 48, 26, .35); border-radius: 3px;
  box-shadow: 0 1px 2px rgba(50, 30, 10, .25);
  transition: transform .16s cubic-bezier(.2, .9, .3, 1), box-shadow .16s;
}
.sw:hover { transform: translateY(-2px); }
/* The chosen swatch is marked by a ring and a lift, never by colour alone -
   the swatches ARE colours, so colour cannot also carry the selection. */
.sw[aria-checked="true"] {
  transform: translateY(-3px) scale(1.12);
  box-shadow: 0 0 0 2px rgba(60, 40, 20, .75), 0 3px 6px rgba(50, 30, 10, .35);
}

.gb-text {
  flex: 1 1 auto; width: 100%; border: 0; background: transparent; resize: none;
  font-family: "Caveat", var(--font); font-size: 1.42rem; line-height: 1.24;
  color: #38302a; outline: none;
}
.gb-text::placeholder { color: rgba(56, 48, 42, .38); }

.gb-row {
  display: flex; align-items: center; gap: .5rem;
  margin-top: .5rem; padding-top: .55rem;
  border-top: 1px solid rgba(70, 48, 26, .22);
}
.gb-name {
  flex: 1 1 auto; min-width: 0; padding: .1rem 0;
  border: 0; background: transparent; outline: none;
  font-family: "Caveat", var(--font); font-size: 1.1rem; color: #4a4038;
}
.gb-name::placeholder { color: rgba(56, 48, 42, .4); }
.gb-count {
  flex: none; min-width: 2.4ch; text-align: right;
  font-family: var(--mono); font-size: .58rem; color: rgba(56, 48, 42, .45);
}
.gb-send {
  flex: none; padding: .38rem .8rem; cursor: pointer;
  border: 1px solid rgba(70, 48, 26, .4); border-radius: 3px;
  background: rgba(255, 255, 255, .45); color: #3d3128;
  font-family: var(--mono); font-size: .6rem; font-weight: 500;
  letter-spacing: .14em; text-transform: uppercase;
  transition: background .18s, transform .18s;
}
.gb-send:hover:not(:disabled) { background: rgba(255, 255, 255, .85); transform: translateY(-1px); }
.gb-send:disabled { opacity: .5; cursor: default; }

.gb-hint, .gb-status {
  font-size: .72rem; line-height: 1.5; color: rgba(255, 246, 232, .85);
  text-shadow: 0 1px 5px rgba(40, 22, 6, .7);
}
.gb-hint { max-width: 286px; }
.gb-hint:empty, .gb-status:empty { display: none; }
.gb-hint.warn, .gb-status.warn { color: #ffd2b8; }
.gb-status { flex: none; padding: .1rem 2.2rem .55rem; }

@media (max-width: 860px) {
  #wall-head { padding: 1.1rem 3.2rem 1rem 1.2rem; }
  .note { width: 148px; min-height: 148px; }
  .note p { font-size: 1.14rem; }
  /* On a phone the note would cover the board, so the composer gives up its
     square and becomes a strip along the bottom. */
  .gb-composer { left: .8rem; right: .8rem; bottom: 1rem; }
  .gb-note-form {
    width: 100%; min-height: 0; transform: none;
    padding: 2.2rem .9rem .8rem;
  }
  .gb-text { min-height: 4.2rem; }
  .gb-hint { max-width: none; }
}

/* ── Projeler ────────────────────────────────────────────────────────────
   The only section that opens on the monitor, so it is the only one written
   in the editor's language rather than on paper. The gutter of line numbers
   down the left is the detail that sells it - and it is the reason this layout
   does not care how many projects there eventually are.

   Scoped to the section rather than to .on-screen: the phone falls back to a
   bottom sheet, and the editor styling should follow it there. */
#panel[data-section="projects"] .repo {
  counter-reset: line;
  list-style: none; margin: 1.2em 0 0; padding: 0;
}
#panel[data-section="projects"] .repo li {
  counter-increment: line;
  position: relative; margin: 0;
  padding: .85em 0 .95em 2.9em;
  border-top: 1px solid rgba(255, 255, 255, .07);
}
#panel[data-section="projects"] .repo li:first-child { border-top: 0; padding-top: .2em; }

#panel[data-section="projects"] .repo li::before {
  content: counter(line, decimal-leading-zero);
  position: absolute; left: 0; top: .95em;
  width: 2em; text-align: right;
  font-size: .85em; color: var(--screen-dim); opacity: .55;
  /* Tabular figures ARE right here: this is a column of numbers that has to
     line up. The opposite rule applies to a large standalone value. */
  font-variant-numeric: tabular-nums;
}
#panel[data-section="projects"] .repo li:first-child::before { top: .3em; }

#panel[data-section="projects"] .repo strong {
  display: block; color: #fff; font-weight: 500; line-height: 1.3;
}
#panel[data-section="projects"] .repo p {
  margin: .3em 0 0; color: #a8b6c8; line-height: 1.65;
}
/* The stack, set like a trailing comment. Enough to read as code without
   turning the description above it into something you have to decode. */
#panel[data-section="projects"] .repo em {
  display: block; margin-top: .45em;
  color: var(--screen-cyan); font-size: .88em; font-style: normal;
}
#panel[data-section="projects"] .repo em::before {
  content: '// '; opacity: .5;
}

#panel[data-section="projects"] .note {
  margin-top: 1.4em; padding-top: 1.1em;
  border-top: 1px solid rgba(255, 255, 255, .09);
  color: var(--screen-dim);
}

/* ── Screen mode ─────────────────────────────────────────────────────────
   Clicking the monitor dollies the camera into the glass, and the panel is
   pinned to it. So it stops being a card and becomes the thing that would
   actually be open on that screen: an editor window. Position and size come
   from the camera every frame - never set them here. */
#panel-chrome { display: none; }

#panel.on-screen {
  right: auto; transform: none; max-height: none;
  border: 0;
  /* Barely rounded. A 5px radius floats a card; a monitor's glass is very
     nearly square at the corners. */
  border-radius: 2px;
  background: var(--screen-bg);
  color: var(--screen-ink);
  /* NO drop shadow. A screen does not cast a shadow onto its own bezel - that
     one property was most of why this read as a window lying on top of the
     room instead of as the room's monitor. What is left is the opposite: light
     coming OUT, spilling onto the bezel and the brick around it. */
  box-shadow:
    0 0 0 1px rgba(0, 0, 0, .55),
    0 0 60px rgba(96, 178, 216, .22),
    0 0 140px rgba(96, 178, 216, .1);
  overflow: hidden;
}
#panel.on-screen[aria-hidden="false"] { transform: none; }
#panel.on-screen::before { display: none; }

/* The glass itself: a sheen raking across from the top left, the faintest
   scanline grid, and darker corners. None of it is readable on its own - all
   three exist so the eye stops reading a rectangle and starts reading a lit
   panel behind glass. */
#panel.on-screen::after {
  content: ''; position: absolute; inset: 0; z-index: 3;
  pointer-events: none; border-radius: inherit;
  background:
    linear-gradient(114deg,
      rgba(255, 255, 255, .07) 0 18%,
      rgba(255, 255, 255, .022) 30%,
      transparent 52%),
    repeating-linear-gradient(0deg,
      rgba(0, 0, 0, .16) 0 1px,
      transparent 1px 3px),
    radial-gradient(120% 120% at 50% 45%,
      transparent 55%, rgba(0, 0, 0, .35) 100%);
}

/* Title bar, with the traffic lights and the file you are looking at. */
#panel.on-screen #panel-chrome {
  display: flex; align-items: center; gap: .38rem; flex: none;
  padding: 0 .7rem; height: 2.05em;
  background: var(--screen-bar);
  border-bottom: 1px solid rgba(255, 255, 255, .06);
}
#panel-chrome i { width: .62em; height: .62em; border-radius: 50%; flex: none; }
#panel-chrome i:nth-child(1) { background: #e0605a; }
#panel-chrome i:nth-child(2) { background: #e0b23f; }
#panel-chrome i:nth-child(3) { background: #5fb07f; }
#panel-file {
  margin-left: .5rem; font-family: var(--mono); font-style: normal;
  font-size: .78em; color: var(--screen-dim); letter-spacing: .02em;
}

/* Everything inside the editor is monospace and one notch smaller: the panel
   is only as wide as the monitor, so the type has to earn its space. */
#panel.on-screen #panel-scroll {
  padding: 1.15em 1.35em 1.5em;
  font-size: clamp(9px, 1.05vw, 15px);
  scrollbar-color: rgba(160, 190, 220, .22) transparent;
}
#panel.on-screen #panel-scroll::-webkit-scrollbar-thumb { background: rgba(160, 190, 220, .2); }

#panel.on-screen #panel-tag { color: var(--screen-cyan); }
#panel.on-screen #panel-tag::before { background: var(--screen-cyan); }
#panel.on-screen #panel-title {
  font-family: var(--mono); font-weight: 500;
  font-size: 1.35em; letter-spacing: -.01em; color: #fff;
  margin: .6em 0 .85em; padding-bottom: .85em;
  border-bottom-color: rgba(255, 255, 255, .09);
}
#panel.on-screen .rich {
  font-family: var(--mono); font-size: .88em; line-height: 1.75;
  color: #b8c5d6;
}
#panel.on-screen .rich strong { color: #fff; font-weight: 500; }
#panel.on-screen .rich em { color: var(--screen-cyan); font-size: .92em; }
#panel.on-screen .rich li::before { background: var(--screen-cyan); }
#panel.on-screen .rich a {
  color: #86c8ec; border-bottom-color: rgba(134, 200, 236, .35);
}
#panel.on-screen .rich a:hover { color: #a9dcfb; border-bottom-color: #a9dcfb; }
#panel.on-screen #panel-close {
  top: .3em; right: .35em; color: var(--screen-dim); font-size: .72em;
}
#panel.on-screen #panel-close:hover { background: rgba(255, 255, 255, .1); color: #fff; }

/* ── Out of the window ─────────────────────────────────────────────────────
   No painted sky of our own: the camera pushes into the view the artist
   already put behind the glass, and this is just the caption riding over it.
   Pointer events stay off the panel itself so the room keeps breathing. */
#outside {
  position: fixed; inset: 0; z-index: 45; pointer-events: none;
  opacity: 0; transition: opacity .6s ease .35s;
}
#outside.show { opacity: 1; }
/* Held still on purpose. An earlier version drifted the panorama sideways to
   show more of it than a 16:9 viewport can hold - which worked while the image
   was pure landscape. It stopped working the moment the artwork grew its own
   window frame: the casement and the stone sill are painted into the picture,
   so panning slid the WINDOW across the screen. You do not move a window by
   looking through it. */
.ow-view {
  position: absolute; inset: 0;
  background: url('outside.webp') center / cover no-repeat;
}
/* The same view after dark, cross-faded by the desk lamp. The room and what is
   outside it are never at different times of day - a lit window in a dark room
   is the single fastest way to break the illusion that this is one place. */
.ow-night {
  background-image: url('outside-night.webp');
  opacity: 0;
  transition: opacity 1.1s ease;
}
body.night .ow-night { opacity: 1; }

/* Weather particles ride over the painting. */
.ow-fx { position: absolute; inset: 0; width: 100%; height: 100%; }

/* Rain and fog also change the air, not just what falls through it. */
#outside[data-weather="rain"]  .ow-view { filter: saturate(.82) brightness(.86); }
#outside[data-weather="storm"] .ow-view { filter: saturate(.7) brightness(.72) contrast(1.05); }
#outside[data-weather="snow"]  .ow-view { filter: saturate(.72) brightness(1.04); }
#outside[data-weather="fog"]   .ow-view { filter: saturate(.55) brightness(1.06) blur(2px); }
#outside[data-weather="fog"]::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(rgba(226, 226, 220, .34), rgba(226, 226, 220, .52));
}

.outside-copy {
  position: absolute; left: 50%; bottom: 8%; transform: translateX(-50%);
  text-align: center; color: #fdf4e2; text-shadow: 0 2px 22px rgba(0, 0, 0, .8);
  padding: 0 1.2rem;
}
.ow-now {
  font-family: var(--mono); font-size: clamp(.72rem, 1.5vw, .88rem);
  font-weight: 500; letter-spacing: .18em; text-transform: uppercase;
}
.ow-now:empty { display: none; }
.ow-hint {
  margin-top: .5rem; font-family: var(--mono); font-size: .58rem;
  letter-spacing: .16em; text-transform: uppercase; opacity: .55;
}

@media (prefers-reduced-motion: reduce) {
  /* .ow-view no longer animates, so nothing to disable here. */
}
#outside-close {
  position: absolute; top: 1.2rem; right: 1.2rem; pointer-events: auto;
  width: 34px; height: 34px; border: 0; border-radius: 50%;
  background: rgba(255, 255, 255, .18); color: #fff;
  font-size: .85rem; cursor: pointer; backdrop-filter: blur(6px);
}
#outside.show #outside-close { pointer-events: auto; }

/* ── The wall clock ──────────────────────────────────────────────────────
   The dial is redrawn rather than just gaining hands, because the painted
   clock already has hands and two sets read as a mistake. Every colour here is
   pulled from the room's own palette and nothing is pure white or pure black:
   a crisp UI clock over a painting looks like a sticker, and the whole point
   is that it should look like it was always there. */
#clock {
  position: fixed; z-index: 14; pointer-events: none;
  opacity: 0; transition: opacity .8s ease .4s, filter .9s ease;
}
#clock.ready { opacity: 1; }
#clock svg { display: block; width: 100%; height: 100%; overflow: visible; }

/* Every value below was read out of the painting with tools/sample.mjs rather
   than picked. The painted hands measure #2d1c0b and the dial around them
   #85603c; matching those is the whole difference between an overlay and a
   sticker. The face itself is a gradient defined in src/clock.js. */
#clock .ticks line { stroke: #33200c; stroke-linecap: round; opacity: .7; }
#clock .hand { stroke: #2a1908; stroke-linecap: round; stroke-width: 4.2; }
#clock .minute { stroke-width: 3.1; }
#clock .pin { fill: #2a1908; }
/* Rust, not red. The dial measures 104 in luminance and this sits at 82, so
   the hand reads as dark-and-warm rather than as the brightest thing in a
   corner whose whole job is to disappear into the wall. */
#clock .second { stroke: #8a3f24; stroke-width: 1.3; }
/* The room is lit from the left, so the hands drop a shadow to the right -
   without it they float a millimetre above a dial they are supposed to be on. */
#clock .hand, #clock .pin, #clock .second { filter: drop-shadow(0.8px 1px 0.6px rgba(20, 10, 2, .45)); }

/* The lamp takes the room to night, and a clock face that stayed bright would
   be the one thing in the picture ignoring it. */
body.night #clock { filter: brightness(.5) saturate(.6); }

/* ── The cat ─────────────────────────────────────────────────────────────
   A sprite sheet over the canvas, not part of the artwork. Position and size
   come from the camera every frame - never set them here. Clicks pass straight
   through, so it can sit on top of a hotspot without stealing it. */
#cat {
  position: fixed; z-index: 15; pointer-events: none;
  background-image: url('cat.webp');
  background-repeat: no-repeat;
  background-size: var(--sheet);
  image-rendering: pixelated;
  animation: cat-idle var(--duration) steps(var(--frames)) infinite;
  opacity: 0;
  transition: opacity .8s ease, filter .9s ease;
}
#cat.ready { opacity: 1; }
@keyframes cat-idle {
  from { background-position-x: 0%; }
  to   { background-position-x: var(--travel); }
}
/* Lights out: the cat is in the room, so it loses the lamp light too. */
body.night #cat { filter: brightness(.42) saturate(.55) hue-rotate(-8deg); }

/* ── Night ───────────────────────────────────────────────────────────────── */
body.night #tooltip { background: rgba(12, 14, 24, .9); }

/* ── noscript fallback ───────────────────────────────────────────────────── */
.fallback { position: fixed; inset: 0; display: grid; place-items: center; background: var(--bg); }
.fallback-inner { max-width: 34rem; padding: 2rem; color: #d8cfc0; text-align: center; line-height: 1.7; }
.fallback-inner h1 { color: #f3ece0; margin-bottom: .6rem; }

/* ── Small screens ───────────────────────────────────────────────────────── */
@media (max-width: 720px) {
  /* A sheet of paper set down at an angle needs room around it. A phone has
     none, so the panel becomes a bottom sheet and drops the rotation. */
  #panel {
    right: 0; left: 0; top: auto; bottom: 0;
    width: 100%;
    /* vh measures the viewport WITHOUT the browser chrome, so on a phone a
       76vh sheet can hang under the address bar. dvh tracks what is really
       on screen; the vh line stays as the fallback. */
    max-height: 76vh; max-height: 76dvh;
    border-radius: 14px 14px 0 0; border-bottom: 0;
    transform: translateY(24px) rotate(0deg);
  }
  #panel[aria-hidden="false"] { transform: translateY(0) rotate(0deg); }
  #panel::before { inset: 0 0 auto 0; width: auto; height: 3px; border-radius: 14px 14px 0 0; }
  #panel-scroll { padding: 1.7rem 1.4rem 2rem; }

  /* The monitor is a few hundred pixels wide on a phone - far too small to
     read an editor in - so screen mode falls back to the same sheet as every
     other section, chrome and all. */
  #panel.on-screen {
    right: 0 !important; left: 0 !important; top: auto !important;
    bottom: 0 !important; width: 100% !important; height: auto !important;
    max-height: 76vh; max-height: 76dvh; border-radius: 14px 14px 0 0;
  }
  #panel.on-screen #panel-chrome { height: 2.4rem; }
  #panel.on-screen #panel-scroll { font-size: 14px; }
}

@media (prefers-reduced-motion: reduce) {
  #panel, #backdrop, #tooltip, #outside { transition-duration: .01ms; }
  .spinner { animation-duration: 3s; }
  /* The cat and the rack indicators are atmosphere, not information - they are
     the first things to go when someone has asked for less movement. Both stay
     visible, they just stop moving. */
  #cat { animation: none; }
  .rich .rack .led { animation: none; }
}
