/* ==========================================================================
   Hariz Sufyan Munawar — portfolio
   Built on the Retro OS design system. Tokens are imported verbatim from
   ds/tokens/; everything below is the component layer, hand-written to the
   same chrome rules as the published .jsx components.
   ========================================================================== */

@import url("ds/tokens/fonts.css");
@import url("ds/tokens/colors.css");
@import url("ds/tokens/typography.css");
@import url("ds/tokens/spacing.css");
@import url("ds/tokens/effects.css");
@import url("ds/tokens/base.css");
@import url("ds/icons.css");

/* --------------------------------------------------------------- primitives */

html, body { height: 100%; }
/* The machine had a 4:3 screen, so this one does too — the desktop is a fixed
   4:3 box centred in whatever window it is given, and the surround is the dark
   nothing outside a CRT's glass. Below the desktop breakpoint the letterbox is
   dropped: a phone held upright has no 4:3 to give. */
body {
  margin: 0;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--teal-900);
  color: var(--text-body);
  font: var(--type-body);
  -webkit-font-smoothing: none;
}
* { box-sizing: border-box; }
p { margin: 0; }
ul { margin: 0; padding: 0; list-style: none; }

:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-offset); }

/* Retro scrollbars — parchment track, beveled thumb, square everything. */
* { scrollbar-color: var(--surface-panel) var(--parchment-200); scrollbar-width: thin; }
::-webkit-scrollbar { width: 15px; height: 15px; }
::-webkit-scrollbar-track { background: var(--parchment-200); background-image: var(--texture-dither); }
::-webkit-scrollbar-thumb {
  background: var(--surface-panel);
  border: 1px solid var(--border-frame);
  box-shadow: var(--bevel-outset-thin);
}
::-webkit-scrollbar-corner { background: var(--parchment-200); }

/* Icons: a stored pixel glyph masked and tinted with a token colour. */
.icon {
  display: inline-block;
  flex: 0 0 auto;
  width: 16px; height: 16px;
  background: var(--ic-color, currentColor);
  -webkit-mask: var(--ic) center / contain no-repeat;
  mask: var(--ic) center / contain no-repeat;
}
/* Four glyphs that live as files rather than inside the generated icons.css.
   They are single-colour art on the same 24-unit grid, so they go through the
   mask like every other glyph — which is what lets them take a tint, and lift
   to parchment on a lit menu row. Left as <img> they would stay black on the
   teal highlight, at 2.7:1. */
.i-game { --ic: url("ds/icons/game.svg"); }
.i-minesweeper { --ic: url("ds/icons/minesweeper.svg"); }
.i-wordle { --ic: url("ds/icons/wordle.svg"); }
.i-terminal { --ic: url("ds/icons/terminal.svg"); }

.icon--12 { width: 12px; height: 12px; }
.icon--14 { width: 14px; height: 14px; }
.icon--18 { width: 18px; height: 18px; }
.icon--26 { width: 26px; height: 26px; }
.icon--28 { width: 28px; height: 28px; }
.icon--44 { width: 44px; height: 44px; }

/* ------------------------------------------------------------------ desktop */

.desktop {
  /* 4:3, as large as fits. One of the two axes always touches the viewport. */
  width: min(100vw, calc(100dvh * 4 / 3));
  height: min(100dvh, calc(100vw * 3 / 4));
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--surface-desktop);
  background-image: var(--texture-dither);
}
.desktop__surface {
  flex: 1;
  position: relative;
  overflow: auto;
}

/* A layer over the whole surface that catches nothing itself — the icons on it
   do. Each one carries its own position, assigned at load and rewritten when
   the visitor drags it. */
.desk-icons {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
}
.desk-icon {
  position: absolute;
  left: var(--ix, var(--space-5));
  top: var(--iy, var(--space-6));
  pointer-events: auto;
  display: grid;
  justify-items: center;
  gap: var(--space-3);
  width: 84px;
  padding: var(--space-2);
  background: none;
  border: none;
  cursor: pointer;
  font: var(--type-caption);
  color: var(--text-body);
  text-align: center;
  touch-action: none;  /* the icon is draggable, so the browser must not pan */
  user-select: none;   /* the second click of a double-click must not select the label */
  -webkit-user-select: none;
}
.desk-icon.is-dragging { z-index: 2; }
/* Stacked-column drag only: the gap left behind while an icon is lifted out
   of the flow, sized to match it so the row it left does not jump. */
.desk-icon__placeholder {
  box-sizing: border-box;
  border: 1px dashed var(--border-frame);
  border-radius: 2px;
}
/* A drawn icon rather than a tinted mask, so it keeps its own colours. */
.desk-icon__img { display: block; image-rendering: pixelated; }
.desk-icon__plate .desk-icon__img { width: 26px; height: 26px; }
.startmenu__item .desk-icon__img { width: 14px; height: 14px; flex: 0 0 auto; }
.desk-icon__plate {
  width: 42px; height: 42px;
  display: grid;
  place-items: center;
  background: var(--surface-card);
  border: 1px solid var(--border-frame);
  box-shadow: var(--bevel-outset-thin);
}
.desk-icon:hover .desk-icon__label,
.desk-icon.is-selected .desk-icon__label {
  background: var(--surface-selected);
  color: var(--text-titlebar);
}
/* Selected is a held state, so it presses the plate in as well — hover alone
   only inverts the label. */
.desk-icon.is-selected .desk-icon__plate {
  background: var(--surface-panel);
  box-shadow: var(--bevel-inset-thin);
}
.desk-icon__label { padding: 0 2px; }

/* ------------------------------------------------------------------- window */

/* --win-w is the size a window opens at; --win-min-w/h is the size below which
   its contents stop being a layout and start being damage. Every window carries
   both, so the resize grips have a floor to stop at without knowing what is
   inside. Windows that need more room than the default say so further down. */
.win {
  --win-min-w: 260px;
  --win-min-h: 160px;
  width: var(--win-w, 420px);
  min-width: var(--win-min-w);
  background: var(--surface-panel);
  border: 1px solid var(--border-strong);
  box-shadow: var(--shadow-window);
}
.desktop__surface > .win {
  position: absolute;
  left: var(--win-x, 120px);
  top: var(--win-y, 40px);
  max-width: 100%;   /* min-width still wins, so this only ever trims the slack */
}
/* Resized by hand, a window carries its own height and its body takes the
   slack — the same bargain a maximised one makes. Until then height is the
   content's business, as it always was. */
.desktop__surface > .win.is-sized {
  height: var(--win-h);
  min-height: var(--win-min-h);
  max-height: 100%;
}
.win[hidden] { display: none !important; }

.win__bar {
  height: var(--titlebar-h);
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0 3px 0 var(--space-4);
  background: var(--surface-titlebar-inactive);
  color: var(--text-titlebar);
  font: var(--type-titlebar);
  cursor: pointer;   /* the bar is the drag handle — say so on hover */
  user-select: none;
}
.win.is-active .win__bar { background: var(--surface-titlebar); }
.win--dialog .win__bar,
.win--dialog.is-active .win__bar { background: var(--surface-titlebar-dialog); }
.win__title {
  flex: 1;
  /* Filename titles carry underscores; a 1.0 line-height clips their descender. */
  line-height: 1.45;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.win__controls { display: flex; gap: 2px; }
.win__ctl {
  width: 16px; height: 14px;
  padding: 0;
  display: grid;
  place-items: center;
  font: var(--weight-bold) 8px/1 var(--font-ui);
  color: var(--text-body);
  background: var(--surface-panel);
  border: 1px solid var(--border-frame);
  box-shadow: var(--bevel-outset-thin);
  cursor: pointer;
}
.win__ctl:active { box-shadow: var(--bevel-inset-thin); }

.win__body {
  padding: var(--space-5);
  font: var(--type-body);
  color: var(--text-body);
}
.win__scroll {
  max-height: min(440px, 56vh);
  overflow-y: auto;
}
.win__footer {
  padding: var(--space-2) var(--space-5) var(--space-5);
  display: flex;
  justify-content: flex-end;
  gap: var(--space-4);
}

/* Maximised: fills the desktop surface, loses its drop shadow. It answers to
   nothing a hand-resize left behind, minimums included — the surface is the
   size, whatever the window would rather be. */
.win.is-max {
  left: 0 !important; top: 0 !important;
  width: 100% !important;
  height: 100% !important;
  min-width: 0 !important;
  min-height: 0 !important;
  box-shadow: var(--shadow-none);
  display: flex;
  flex-direction: column;
}
/* Both states hand the window a height it did not choose, so both need the
   body to absorb the difference rather than spill out of the frame. */
.win:is(.is-max, .is-sized) { display: flex; flex-direction: column; }
.win:is(.is-max, .is-sized) .win__body { flex: 1; min-height: 0; overflow-y: auto; }
.win:is(.is-max, .is-sized) .win__scroll { max-height: none; }

/* Where a window carried to the top of the desktop would land if it were let go
   of. An outline and nothing else, the way this era drew a window on the move:
   filling it would wash over the whole desktop, and the promise is about the
   edges anyway. */
.snap-ghost {
  position: absolute;
  inset: 0;
  pointer-events: none;
  border: 3px dashed var(--teal-800);
}

/* ---------------------------------------------------------- resize grips -- */
/* Eight strips laid over the frame edge, corners on top of sides, and the one
   in the bottom-right drawn as the ridged tab every window of this era wore.
   They exist only on the free-placed desktop: in the stacked column a window is
   as wide as the column and there is nothing to drag. A maximised window has
   given up its size, so it drops them too. */

.win__grab { display: none; }
.desktop__surface > .win > .win__grab {
  display: block;
  position: absolute;
  z-index: 3;
  touch-action: none;
}
.desktop__surface > .win.is-max > .win__grab { display: none; }

/* Every strip sits inside the frame rather than straddling it. A grip that hung
   over the edge would be part of the surface's scrollable area, and a window
   pushed against the bottom of the desktop would raise a scrollbar for the sake
   of three pixels nobody can see. */
.win__grab--n  { top: 0; left: 12px; right: 12px; height: 5px; cursor: ns-resize; }
.win__grab--s  { bottom: 0; left: 12px; right: 12px; height: 5px; cursor: ns-resize; }
.win__grab--w  { left: 0; top: 12px; bottom: 12px; width: 5px; cursor: ew-resize; }
.win__grab--e  { right: 0; top: 12px; bottom: 12px; width: 5px; cursor: ew-resize; }
.win__grab--nw { top: 0; left: 0; width: 12px; height: 12px; cursor: nwse-resize; }
.win__grab--ne { top: 0; right: 0; width: 12px; height: 12px; cursor: nesw-resize; }
.win__grab--sw { bottom: 0; left: 0; width: 12px; height: 12px; cursor: nesw-resize; }
.win__grab--se { bottom: 0; right: 0; width: 14px; height: 14px; cursor: nwse-resize; }
/* Three ridges, lit top-left and shadowed bottom-right like every other raised
   edge in the system. Drawn on the corner itself so it reads as part of the
   frame rather than as something sitting on the content. */
.win__grab--se::after {
  content: "";
  position: absolute;
  inset: 3px 2px 2px 3px;
  background: repeating-linear-gradient(135deg,
    var(--bevel-light) 0 1px,
    var(--bevel-deep) 1px 2px,
    transparent 2px 4px);
}
/* Nothing underneath should react while a window is being sized — a drag that
   crosses a link or a file row is a drag, not a click. */
.win.is-resizing, .win.is-resizing * { user-select: none; }
.win.is-resizing .win__body { pointer-events: none; }

/* Floors. Each is the point past which that window's own contents stop fitting:
   a column that would collapse, a board that cannot reflow, a form whose labels
   would wrap to nothing. Anything not named here keeps the 260x160 default. */
#win-about      { --win-min-w: 320px; --win-min-h: 220px; }
#win-projects   { --win-min-w: 440px; --win-min-h: 300px; }  /* four columns + chrome */
#win-recycle    { --win-min-w: 380px; --win-min-h: 260px; }
#win-resume     { --win-min-w: 380px; --win-min-h: 260px; }  /* the tab strip is one row */
#win-contact    { --win-min-w: 340px; --win-min-h: 260px; }
#win-player     { --win-min-w: 300px; --win-min-h: 240px; }
#win-viewer     { --win-min-w: 340px; --win-min-h: 280px; }
/* The two games are drawn on fixed grids — 10x22px cells, 5x44px tiles, a
   keyboard of 30px keys — and none of it reflows, so their floor is the size
   they open at to the pixel: they may be given room, never taken it. Anything
   less and the board is complete but the keyboard is under the frame. */
#win-minesweeper { --win-min-w: 252px; --win-min-h: 318px; }
#win-wordle      { --win-min-w: 300px; --win-min-h: 511px; }
/* Eighty columns, which is what a terminal is: 512px of VT323 at 6.4px a
   character, plus the screen's padding, frame and scrollbar, plus the window's
   own. The shell lays every column it prints out to eighty, so anything less
   is a window that wraps its own output — the games' bargain again, room to be
   given and none to be taken. */
#win-terminal    { --win-min-w: 572px; --win-min-h: 260px; }
.win--doc        { --win-min-w: 320px; --win-min-h: 220px; }

/* -------------------------------------------------------------------- panel */

.panel {
  background: var(--surface-panel);
  border: 1px solid var(--border-strong);
  box-shadow: var(--shadow-raised);
}
.panel__label {
  height: var(--panel-label-h);
  display: flex;
  align-items: center;
  padding: 0 var(--space-4);
  background: var(--surface-titlebar);
  color: var(--text-titlebar);
  font: var(--type-panel-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}
.panel__body {
  padding: var(--space-5);
  display: grid;
  gap: var(--space-5);
}

/* ------------------------------------------------------------------- button */

.btn {
  height: var(--control-h-md);
  min-width: 88px;
  padding: 0 var(--space-5);
  font: var(--type-ui);
  color: var(--text-body);
  background: var(--surface-panel);
  border: 1px solid var(--border-frame);
  box-shadow: var(--bevel-outset);
  cursor: pointer;
  outline-offset: var(--focus-offset);
}
.btn:hover { background: var(--parchment-200); }
.btn:active {
  box-shadow: var(--bevel-inset);
  padding: 2px 0 0 calc(var(--space-5) + 1px);
  padding-right: calc(var(--space-5) - 1px);
}
.btn--accent {
  background: var(--accent-action);
  color: var(--text-titlebar);
}
.btn--accent:hover { background: var(--teal-600); }
.btn--sm { height: var(--control-h-sm); min-width: 0; font: var(--type-caption); padding: 0 var(--space-4); }
/* A link wearing the button's chrome still has to centre its label: .btn is
   sized for a control, and an anchor is not one. */
a.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
  color: var(--text-body);
}
a.btn:visited { color: var(--text-body); }
/* Centred. `a.btn` above already does that to the ones that are links; saying
   it here as well is what reaches a row that is not a link, so a span in the
   set sits like its neighbours instead of against the left edge. */
.btn--link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-4);
  justify-content: center;
  width: 100%;
  height: var(--control-h-lg);
  text-decoration: none;
  color: var(--text-body);
}
.btn--link:hover { color: var(--text-body); background: var(--parchment-200); }
.btn--link:visited { color: var(--text-body); }
/* Except in the Email window, where the three are a list of details rather than
   a row of actions and read from the left. It takes the window's id to say so,
   because `a.btn` outranks a class on its own: without it the two that are
   links would stay centred and the address, being the only span, would be the
   one row that moved. */
#win-contact .btn--link { justify-content: flex-start; }

/* -------------------------------------------------------------------- input */

.field {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  width: 100%;
  height: var(--control-h-lg);
  padding: 0 var(--space-4);
  background: var(--surface-input);
  border: 1px solid var(--border-frame);
  box-shadow: var(--bevel-inset-thin);
}
.field:focus-within { border-color: var(--teal-600); }
.field input,
.field textarea,
.field select {
  flex: 1;
  min-width: 0;
  border: none;
  outline: none;
  background: transparent;
  font: var(--type-ui);
  color: var(--text-body);
  resize: none;
}
/* A select keeps none of the platform chrome a text field never had either —
   the same flat, bevel-inset field the rest of the form uses, with the
   system's own chevron standing in for the browser's. */
.field select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: var(--space-6);
  background-image: var(--ic-chevron-down);
  background-repeat: no-repeat;
  background-position: right center;
  background-size: 14px 14px;
  cursor: pointer;
}
.field--area { height: auto; padding: var(--space-4); align-items: flex-start; }
.field--area textarea { line-height: var(--leading-body); }
.field__label {
  display: block;
  font: var(--type-caption);
  color: var(--text-muted);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  margin-bottom: var(--space-3);
}

/* --------------------------------------------------------------------- tabs */

.tabs__strip {
  display: flex;
  gap: var(--space-1);
  position: relative;
  z-index: 1;
}
.tab {
  margin-top: 2px;
  padding: 4px var(--space-5);
  font: var(--type-ui);
  color: var(--text-muted);
  background: var(--surface-panel);
  border: 1px solid var(--border-frame);
  box-shadow: var(--bevel-outset-thin);
  cursor: pointer;
}
.tab[aria-selected="true"] {
  margin-top: 0;
  padding: 5px var(--space-5) 6px;
  font: var(--weight-bold) var(--text-sm)/1.35 var(--font-ui);
  color: var(--text-body);
  background: var(--surface-card);
  border-bottom-color: var(--surface-card);
}
.tabs__body {
  margin-top: -1px;
  padding: var(--space-5);
  background: var(--surface-card);
  border: 1px solid var(--border-frame);
  box-shadow: var(--bevel-outset-thin);
  font: var(--type-body);
}
.tabs__panel { display: grid; gap: var(--space-6); }
.tabs__panel[hidden] { display: none; }

/* --------------------------------------------------------------------- list */
/* The design system's hairline-divided row. Pointing at one inverts it and
   selecting holds that inversion, so a row under the cursor and a row that is
   chosen read the same — which is exactly how a shell list behaves. */

.list__row {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  width: 100%;
  min-height: 34px;
  padding: var(--space-4) var(--space-5);
  border: none;
  border-bottom: 1px solid var(--border-hairline);
  background: transparent;
  font: var(--type-ui);
  color: var(--text-body);
  text-align: left;
  cursor: pointer;
}
.list__row:last-child { border-bottom: none; }
.list__row[hidden] { display: none; }
.list__row:hover,
.list__row[aria-selected="true"] {
  background: var(--surface-selected);
  color: var(--text-titlebar);
}
/* An inline --ic-color would outrank this, so rows tint their glyphs from the
   stylesheet — otherwise a selected row's icon goes teal on teal. */
.list__row:hover .icon,
.list__row[aria-selected="true"] .icon { --ic-color: var(--parchment-50); }

/* --------------------------------------------------------- meter + slider */
/* The design system's two horizontal readouts. Both are a well with a teal fill
   in it; the difference is that a meter only reports and a slider is dragged,
   so a slider has a thumb to take hold of.
     --pct is the fill width, set from script as a percentage. */

.meter {
  position: relative;
  height: 16px;
  padding: 2px;
  overflow: hidden;
  background: var(--surface-input);
  border: 1px solid var(--border-frame);
  box-shadow: var(--bevel-inset-thin);
}
.meter__fill { height: 100%; width: var(--pct, 0%); background: var(--accent-action); }
/* The DOS meter: the fill is cut into 7px blocks with 3px of well between them,
   so it counts up in steps rather than sliding. */
.meter--blocks .meter__fill {
  background: repeating-linear-gradient(90deg, var(--accent-action) 0 7px, transparent 7px 10px);
}
/* A meter that can be scrubbed carries a range input over the whole of itself.
   The input does the dragging, the keyboard and the semantics; the blocks below
   it do the drawing. */
.meter input[type="range"] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
.meter input[type="range"]:disabled { cursor: default; }
.meter:focus-within { border-color: var(--teal-600); }

.slider {
  position: relative;
  flex: 1;
  min-width: 60px;
  height: 22px;
}
.slider__track {
  position: absolute;
  inset: 7px 0;
  background: var(--surface-input);
  border: 1px solid var(--border-frame);
  box-shadow: var(--bevel-inset-thin);
}
.slider__fill {
  position: absolute;
  left: 1px; top: 8px; bottom: 8px;
  width: max(0px, calc(var(--pct, 0%) - 2px));
  background: var(--accent-action);
}
/* The thumb is a raised control and nothing else — the input under it takes
   every pointer event, so the thumb never has to. */
.slider__thumb {
  position: absolute;
  top: 0;
  left: calc(var(--pct, 0%) - 7px);
  width: 14px; height: 22px;
  background: var(--surface-panel);
  border: 1px solid var(--border-frame);
  box-shadow: var(--bevel-outset-thin);
  pointer-events: none;
}
.slider input[type="range"] {
  position: absolute;
  inset: 0;
  width: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
.slider:focus-within .slider__thumb { outline: var(--focus-ring); outline-offset: -3px; }
.slider__value {
  flex: 0 0 auto;
  min-width: 30px;
  font: var(--type-caption);
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* --------------------------------------------------- toolbar and its menus */
/* Shell chrome: a control is flat until pointed at, then it grows a bevel.
   That is the era's affordance — chrome appears under the cursor, it does not
   sit there waiting. An open menu holds its button lit instead. */

.toolbar {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-2);
  background: var(--surface-panel);
  box-shadow: var(--bevel-outset-thin);
}

.flatbtn {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  height: var(--control-h-sm);
  padding: 0 var(--space-4);
  font: var(--type-caption);
  color: var(--text-body);
  background: transparent;
  border: 1px solid transparent;
  cursor: pointer;
  white-space: nowrap;
  outline-offset: var(--focus-offset);
}
.flatbtn .icon { --ic-color: var(--teal-700); }
.flatbtn:hover {
  background: var(--parchment-200);
  border-color: var(--border-frame);
  box-shadow: var(--bevel-outset-thin);
}
.flatbtn:active {
  background: var(--parchment-200);
  border-color: var(--border-frame);
  box-shadow: var(--bevel-inset-thin);
}
.flatbtn[aria-expanded="true"] {
  background: var(--surface-selected);
  color: var(--text-titlebar);
  border-color: var(--border-frame);
  box-shadow: none;
}
/* A toolbar control with nothing left to do greys out and stops answering the
   cursor — chrome that appears under the pointer must not appear under it when
   pressing would do nothing. A link acting as one has no :disabled, so losing
   its href is what says the same thing. */
.flatbtn:disabled,
a.flatbtn:not([href]) {
  color: var(--text-disabled);
  cursor: default;
  pointer-events: none;
}
.flatbtn:disabled .icon,
a.flatbtn:not([href]) .icon { --ic-color: var(--text-disabled); }
a.flatbtn { text-decoration: none; }
a.flatbtn:visited { color: var(--text-body); }

/* A menu-bar menu: it drops from the button's own left edge, so the trail back
   to what opened it is a straight line. Unlike the rest of the toolbar the
   button keeps its bevel and its caret at rest — it is the only control in the
   window, and a control nobody can see is a control nobody presses. */
.menu { position: relative; }
.menu__btn {
  padding: 0 var(--space-4);
  background: var(--surface-panel);
  border-color: var(--border-frame);
  box-shadow: var(--bevel-outset-thin);
}
.menu__btn:hover { background: var(--parchment-200); }
.menu__btn[aria-expanded="true"] {
  background: var(--surface-selected);
  color: var(--text-titlebar);
  box-shadow: var(--bevel-inset-thin);
}
.menu__btn[aria-expanded="true"] .icon { --ic-color: var(--parchment-50); }
.menu__caret { --ic-color: var(--text-muted); margin-left: var(--space-1); }
/* The home button is the same class of control, so it reads the same way. */
.addrbar__home {
  background: var(--surface-panel);
  border-color: var(--border-frame);
  box-shadow: var(--bevel-outset-thin);
}
.menu__pop {
  position: absolute;
  left: 0;
  top: 100%;
  z-index: 20;
  min-width: 140px;
  background: var(--surface-panel);
  border: 1px solid var(--border-strong);
  box-shadow: var(--shadow-window);
}
.menu__pop[hidden] { display: none; }
.menu__item {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  width: 100%;
  padding: var(--space-3) var(--space-5);
  border: none;
  background: transparent;
  font: var(--type-ui);
  color: var(--text-body);
  text-align: left;
  cursor: pointer;
}
.menu__item .icon { --ic-color: var(--teal-700); }
.menu__item:hover { background: var(--surface-selected); color: var(--text-titlebar); }
.menu__item:hover .icon { --ic-color: var(--parchment-50); }
/* The tick keeps its space when it is not showing, so the labels stay in one
   column whichever view is current. */
.menu__tick { visibility: hidden; }
.menu__item[aria-checked="true"] .menu__tick { visibility: visible; }

/* A toolbar that runs to both edges needs somewhere to put the slack. Neither
   of these is a control — they are the gaps between groups of them. */
.toolbar__gap { flex: 0 0 var(--space-5); }
.toolbar__grow { flex: 1; }

/* --------------------------------------------------------------- addr bar */

.addrbar { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-3) var(--space-2) 0; }
.addrbar__home { padding: 0 var(--space-3); }
/* Up is the down chevron through a half turn. 180° keeps every pixel on its
   own pixel; a quarter turn would not, for art drawn on a grid this coarse. */
.addrbar__up { transform: rotate(180deg); }
.addrbar__field {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  height: var(--control-h-sm);
  padding: 0 var(--space-4);
  background: var(--surface-input);
  border: 1px solid var(--border-frame);
  box-shadow: var(--bevel-inset-thin);
}
.addrbar__field:focus-within { border-color: var(--teal-600); }
.addrbar__field input {
  flex: 1;
  min-width: 0;
  border: none;
  outline: none;
  background: transparent;
  font: var(--type-ui);
  color: var(--text-body);
}
/* A path the shell cannot reach turns the well red until it is fixed. */
.addrbar__field.is-bad { border-color: var(--brick-500); }
.addrbar__field.is-bad input { color: var(--brick-500); }

/* -------------------------------------------------------------- status bar */

.statusbar {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2);
  background: var(--surface-panel);
  box-shadow: var(--bevel-outset-thin);
}
/* A static height, because an empty well has no line box and would otherwise
   collapse to its own padding while the well beside it stays the height of its
   text. The number is what a line of caption comes to: 14px of leading in 2px
   of padding, top and bottom. */
.statusbar__cell {
  height: 18px;
  padding: 2px var(--space-4);
  font: var(--type-caption);
  color: var(--text-muted);
  box-shadow: var(--bevel-inset-thin);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.statusbar__cell--grow { flex: 1; min-width: 0; }
/* A fixed width, not a minimum: the well is a part of the chrome and stays the
   same size whether it is holding a total, a file's size or nothing at all.
   Sized to the widest thing it ever says — 1023.9KB, the step before MB. */
.statusbar__cell--size { flex: 0 0 84px; text-align: right; }

/* ------------------------------------------------------------------ taskbar */

.taskbar {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  height: 30px;
  padding: var(--space-2);
  background: var(--surface-panel);
  border-top: 1px solid var(--bevel-light);
  box-shadow: var(--bevel-outset-thin);
  position: relative;
  z-index: 90;
}
.taskbar__start {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  height: 24px;
  padding: 0 var(--space-4);
  font: var(--weight-bold) var(--text-sm)/1 var(--font-ui);
  color: var(--text-body);
  background: var(--surface-panel);
  border: 1px solid var(--border-frame);
  box-shadow: var(--bevel-outset);
  cursor: pointer;
}
.taskbar__start[aria-expanded="true"] { box-shadow: var(--bevel-inset); }
/* The one four-colour mark on the page: a pixel flag on a 16-unit grid, drawn
   at 16px so one unit is one whole pixel and the steps stay hard-edged. */
.start-logo { width: 16px; height: 16px; flex: 0 0 auto; }
.start-logo__a { fill: var(--brick-500); }
.start-logo__b { fill: var(--green-500); }
.start-logo__c { fill: var(--blue-600); }
.start-logo__d { fill: var(--amber-500); }
.taskbar__items { flex: 1; display: flex; gap: var(--space-2); overflow: hidden; }
.taskbar__item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  height: 24px;
  max-width: 160px;
  padding: 0 var(--space-4);
  font: var(--type-caption);
  color: var(--text-body);
  background: var(--surface-panel);
  border: 1px solid var(--border-frame);
  box-shadow: var(--bevel-outset-thin);
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.taskbar__item.is-active { box-shadow: var(--bevel-inset-thin); background: var(--parchment-200); }
.taskbar__clock {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  height: 24px;
  padding: 0 var(--space-4);
  font: var(--type-caption);
  box-shadow: var(--bevel-inset-thin);
}

/* --------------------------------------------------------------- start menu */

.startmenu {
  position: absolute;
  left: var(--space-2);
  bottom: calc(30px + var(--space-1));
  width: 190px;
  background: var(--surface-panel);
  border: 1px solid var(--border-strong);
  box-shadow: var(--shadow-window);
  z-index: 95;
}
.startmenu[hidden] { display: none; }
.startmenu__brand {
  padding: var(--space-4);
  background: var(--surface-statusbar);
  color: var(--parchment-100);
  font: var(--weight-bold) var(--text-sm)/1 var(--font-display);
  letter-spacing: var(--tracking-display);
}
.startmenu__item {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  width: 100%;
  padding: var(--space-4);
  border: none;
  background: transparent;
  font: var(--type-ui);
  color: var(--text-body);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}
/* Marks are tinted from here rather than inline, because an inline --ic-color
   would outrank the highlight rule below and a lit row would keep a teal glyph
   on a teal ground. Same reason .list__row does it. Specificity does the
   ordering: one class less than the rules that light a row. */
.startmenu__item .i-mail,
.startmenu__item .i-camera { --ic-color: var(--teal-600); }
.startmenu__item .i-external-link { --ic-color: var(--teal-700); }
.startmenu__item .i-trash { --ic-color: var(--parchment-700); }
.startmenu__item .i-game,
.startmenu__item .i-music { --ic-color: var(--blue-600); }
.startmenu__item .i-image { --ic-color: var(--amber-700); }
.startmenu__item .i-minesweeper { --ic-color: var(--brick-500); }
.startmenu__item .i-wordle { --ic-color: var(--green-700); }
/* A screen, so it wears the colour this system paints screens: the status bar's
   own teal, which is what the terminal window is lit with. */
.startmenu__item .i-terminal { --ic-color: var(--teal-800); }
.startmenu__item .i-sliders { --ic-color: var(--teal-700); }
/* The caret is chrome rather than a mark, so it stays quiet until the row is. */
.startmenu__item .i-chevron-right { --ic-color: var(--text-muted); }

.startmenu__item:hover {
  background: var(--surface-selected);
  color: var(--text-titlebar);
}
.startmenu__item:hover .icon { --ic-color: var(--parchment-50); }
.startmenu__sep { height: 1px; margin: var(--space-1) var(--space-2); background: var(--border-hairline); }

/* A submenu flies out to the right of its parent row, which stays lit while the
   flyout is open so the trail back to it reads. */
.startmenu__group { position: relative; }
.startmenu__text { flex: 1; }
.startmenu__item--sub[aria-expanded="true"] {
  background: var(--surface-selected);
  color: var(--text-titlebar);
}
.startmenu__item--sub[aria-expanded="true"] .icon { --ic-color: var(--parchment-50); }
.startmenu__sub {
  position: absolute;
  left: 100%;
  top: calc(var(--space-1) * -1);
  width: 160px;
  background: var(--surface-panel);
  border: 1px solid var(--border-strong);
  box-shadow: var(--shadow-window);
  z-index: 96;
}
.startmenu__sub[hidden] { display: none; }

/* -------------------------------------------------------------------- modal */

.scrim {
  position: absolute;
  inset: 0;
  background: rgba(20, 49, 49, 0.35);
  display: grid;
  place-items: center;
  z-index: 100;
}
.scrim[hidden] { display: none; }
.win--dialog { width: 320px; box-shadow: var(--shadow-dialog); }
.dialog__row { display: flex; gap: var(--space-5); align-items: flex-start; }
.dialog__msg { padding-top: var(--space-2); }

/* -------------------------------------------------------------------- toast */

.toast {
  position: absolute;
  right: var(--space-7);
  bottom: calc(30px + var(--space-6));
  display: inline-flex;
  align-items: center;
  gap: var(--space-5);
  padding: var(--space-5) var(--space-6);
  background: var(--surface-card);
  border: 1px solid var(--border-frame);
  box-shadow: var(--bevel-outset);
  font: var(--type-ui);
  z-index: 85;
}
.toast[hidden] { display: none; }

/* -------------------------------------------------------------- alerts */
/* A stack in the bottom-right corner, above the task bar and clear of the
   toast. Cards arrive at the bottom, so the newest is nearest the corner and
   the ones above it keep their place rather than shuffling. */

.alerts {
  position: absolute;
  right: var(--space-6);
  bottom: calc(30px + var(--space-5));
  z-index: 88;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: var(--space-4);
  pointer-events: none;   /* the gaps between cards are not a wall */
}
.alert {
  /* The palette's danger red is tuned for parchment; on this dark panel it
     falls to about 2.3:1, and lifting it toward parchment turns it to salmon
     rather than making it legible. This is the same hue pitched for a dark
     ground — component-level, so the token itself is left alone — and any
     alert can override it by passing an accent. */
  --alert-accent: #ef5340;
  pointer-events: auto;
  width: 304px;
  padding: var(--space-4) var(--space-5) var(--space-6);
  background: var(--surface-statusbar);
  border: 1px solid var(--border-strong);
  box-shadow: var(--shadow-dialog);
  display: grid;
  gap: var(--space-4);
}
.alert__bar { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); }
.alert__icon { width: 22px; height: 22px; display: block; image-rendering: pixelated; }
.alert__x {
  width: 16px; height: 14px;
  padding: 0;
  display: grid;
  place-items: center;
  font: var(--weight-bold) 8px/1 var(--font-ui);
  color: var(--text-body);
  background: var(--surface-panel);
  border: 1px solid var(--border-frame);
  box-shadow: var(--bevel-outset-thin);
  cursor: pointer;
}
.alert__x:hover { background: var(--parchment-200); }
.alert__x:active { box-shadow: var(--bevel-inset-thin); }
.alert__title {
  font: var(--weight-bold) var(--text-lg)/var(--leading-tight) var(--font-ui);
  color: var(--alert-accent);
  letter-spacing: var(--tracking-label);
  text-align: center;
}
.alert__body {
  font: var(--type-caption);
  line-height: var(--leading-body);
  color: var(--parchment-50);
  text-align: center;
}

/* --------------------------------------------------------- content patterns */

.identity { display: flex; gap: var(--space-6); }
.identity__plate {
  width: 86px; height: 86px;
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  background: var(--surface-input);
  background-image: var(--texture-dither);
  border: 1px solid var(--border-frame);
  box-shadow: var(--bevel-inset-thin);
}
/* The one image on the page, so it opts out of the global pixelated rendering:
   nearest-neighbour on a photo scaled down this far reads as damage, not as
   pixel art. The frame around it carries the era instead. */
.identity__photo {
  width: 100%; height: 100%;
  display: block;
  object-fit: cover;
  image-rendering: auto;
}
.identity__name { font: var(--type-title); color: var(--ink); }
.identity__role {
  margin-top: 4px;
  font: var(--type-caption);
  color: var(--text-muted);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}

.stack { display: grid; gap: var(--space-4); }
.stack--tight { gap: var(--space-3); }
.stack--wide { gap: var(--space-6); }
.section-label {
  font: var(--type-caption);
  color: var(--text-muted);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}
.rule { height: 1px; background: var(--border-hairline); box-shadow: 0 1px 0 var(--chrome-light); }

.job__head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-4); }
.job__org { font: var(--weight-bold) var(--text-md)/var(--leading-ui) var(--font-ui); }
.job__where { font: var(--type-caption); color: var(--text-muted); text-align: right; }
.job__role {
  display: flex;
  justify-content: space-between;
  gap: var(--space-4);
  margin-top: 2px;
  font: var(--type-ui);
  color: var(--text-muted);
}
/* A product inside a job — one employer, three platforms. It has to sit under
   the role without competing with the organisation, so it takes the UI size at
   bold and its descriptor stays a muted caption alongside it. */
.job__product {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-4);
  margin-top: var(--space-5);
  font: var(--weight-bold) var(--text-sm)/var(--leading-ui) var(--font-ui);
}
.job__product span { font: var(--type-caption); color: var(--text-muted); }
.job__product + .bullets { margin-top: var(--space-3); }

.bullets { display: grid; gap: var(--space-3); margin-top: var(--space-4); }
.bullets li {
  display: flex;
  gap: var(--space-4);
  font: var(--type-ui);
  line-height: var(--leading-body);
}
.bullets li::before {
  content: "\25A0";
  flex: 0 0 auto;
  color: var(--teal-500);
  font-size: var(--text-2xs);
  line-height: 1.7;
}

.kv { display: grid; grid-template-columns: 96px 1fr; gap: var(--space-3) var(--space-5); font: var(--type-ui); }
.kv dt { color: var(--text-muted); letter-spacing: var(--tracking-label); text-transform: uppercase; font-size: var(--text-xs); padding-top: 1px; }
.kv dd { margin: 0; }

/* -------------------------------------------------------------- minesweeper */
/* A 10x10 board on a 22px cell grid. Covered cells are raised controls, cleared
   ones are flat wells — the whole game is the bevel language doing its job. */

.ms { display: grid; gap: var(--space-4); justify-items: center; }
.ms__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* Matches the board exactly: 10 cells x 22px + 2px well padding + 1px frame. */
  width: 100%;
  max-width: 226px;
  padding: var(--space-2) var(--space-3);
  background: var(--surface-panel);
  border: 1px solid var(--border-frame);
  box-shadow: var(--bevel-inset-thin);
}
.ms__readout {
  min-width: 46px;
  padding: 0 var(--space-2);
  font: var(--type-terminal);
  line-height: 1.2;
  text-align: center;
  color: var(--green-400);
  background: var(--surface-statusbar);
  border: 1px solid var(--border-frame);
  box-shadow: var(--bevel-inset-thin);
  font-variant-numeric: tabular-nums;
}
.ms__face {
  width: 26px; height: 26px;
  padding: 0;
  font: var(--weight-bold) var(--text-sm)/1 var(--font-ui);
  color: var(--text-body);
  background: var(--surface-panel);
  border: 1px solid var(--border-frame);
  box-shadow: var(--bevel-outset);
  cursor: pointer;
  outline-offset: var(--focus-offset);
}
.ms__face:hover { background: var(--parchment-200); }
.ms__face:active { box-shadow: var(--bevel-inset); }

.ms__grid {
  display: grid;
  grid-template-columns: repeat(10, 22px);
  padding: 2px;
  background: var(--surface-panel);
  border: 1px solid var(--border-frame);
  box-shadow: var(--bevel-inset-thin);
}
.ms__cell {
  width: 22px; height: 22px;
  display: grid;
  place-items: center;
  padding: 0;
  border: none;
  background: var(--surface-panel);
  box-shadow: var(--bevel-outset-thin);
  font: var(--weight-bold) var(--text-sm)/1 var(--font-ui);
  color: var(--text-body);
  cursor: pointer;
  outline-offset: -2px;
}
.ms__cell:hover { background: var(--parchment-200); }
.ms__cell.is-clear {
  background: var(--parchment-200);
  box-shadow: inset 0 0 0 1px var(--border-hairline);
  cursor: default;
}
.ms__cell.is-blast { background: var(--brick-500); }
.ms__cell[data-n="1"] { color: var(--blue-600); }
.ms__cell[data-n="2"] { color: var(--green-700); }
.ms__cell[data-n="3"] { color: var(--brick-500); }
.ms__cell[data-n="4"] { color: var(--blue-700); }
.ms__cell[data-n="5"] { color: var(--amber-700); }
.ms__cell[data-n="6"] { color: var(--teal-600); }
.ms__cell[data-n="7"] { color: var(--ink); }
.ms__cell[data-n="8"] { color: var(--parchment-800); }

/* Mine: a block with four spikes. Flag: a stepped pennant on a pole, drawn on a
   14-unit grid at 14px so one unit is one whole pixel. */
.ms__mine { position: relative; width: 8px; height: 8px; background: var(--ink); }
.ms__mine::before, .ms__mine::after { content: ""; position: absolute; background: var(--ink); }
.ms__mine::before { top: 3px; left: -3px; width: 14px; height: 2px; }
.ms__mine::after { top: -3px; left: 3px; width: 2px; height: 14px; }
.is-blast .ms__mine, .is-blast .ms__mine::before, .is-blast .ms__mine::after { background: var(--parchment-50); }
.ms__flag { width: 14px; height: 14px; }
.ms__flag__cloth { fill: var(--brick-500); }
.ms__flag__pole { fill: var(--ink); }
.ms__flag--wrong .ms__flag__cloth { fill: var(--ink-disabled); }

/* ------------------------------------------------------------------- wordle */
/* Board is 5 x 44px tiles; the key rows are sized to sit inside that same
   274px of body width without wrapping. */

.wd { display: grid; gap: var(--space-4); justify-items: center; }
.wd__board { display: grid; gap: var(--space-3); }
.wd__row { display: grid; grid-template-columns: repeat(5, 44px); gap: var(--space-3); }
.wd__tile {
  width: 44px; height: 44px;
  display: grid;
  place-items: center;
  font: var(--weight-bold) var(--text-2xl)/1 var(--font-display);
  color: var(--text-body);
  background: var(--surface-input);
  border: 1px solid var(--border-frame);
  box-shadow: var(--bevel-inset-thin);
  text-transform: uppercase;
}
.wd__tile.is-filled { background: var(--surface-card); }

.wd__keys { display: grid; gap: var(--space-1); width: 100%; }
.wd__krow { display: flex; gap: 3px; justify-content: center; }
.wd__key {
  width: 24px; height: 30px;
  display: grid;
  place-items: center;
  padding: 0;
  font: var(--weight-bold) var(--text-xs)/1 var(--font-ui);
  color: var(--text-body);
  background: var(--surface-panel);
  border: 1px solid var(--border-frame);
  box-shadow: var(--bevel-outset-thin);
  cursor: pointer;
  text-transform: uppercase;
  outline-offset: -3px;
}
.wd__key:hover { background: var(--parchment-200); }
.wd__key:active { box-shadow: var(--bevel-inset-thin); }
.wd__key--wide { width: 38px; font-size: var(--text-2xs); }

/* Scored states are shared by tiles and keys: green placed, amber elsewhere in
   the word, grey spent. All three keep ink or parchment text above 4.5:1. */
.wd__tile.is-correct, .wd__key.is-correct {
  background: var(--green-700);
  color: var(--parchment-50);
  border-color: var(--border-strong);
  box-shadow: var(--bevel-outset-thin);
}
.wd__tile.is-present, .wd__key.is-present {
  background: var(--amber-500);
  color: var(--ink);
  border-color: var(--border-strong);
  box-shadow: var(--bevel-outset-thin);
}
.wd__tile.is-absent, .wd__key.is-absent {
  background: var(--parchment-500);
  color: var(--ink);
  border-color: var(--border-frame);
  box-shadow: var(--bevel-inset-thin);
}
.wd__key.is-absent { color: var(--ink-soft); }
.wd__key.is-correct .icon, .wd__key.is-present .icon, .wd__key.is-absent .icon { --ic-color: currentColor; }

.wd__msg {
  min-height: 15px;
  font: var(--type-caption);
  color: var(--text-muted);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  text-align: center;
}
.wd__msg.is-alert { color: var(--brick-500); }

.chips { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.chip {
  padding: 2px var(--space-4);
  font: var(--type-caption);
  background: var(--surface-card);
  border: 1px solid var(--border-frame);
  box-shadow: var(--bevel-outset-thin);
}

/* ----------------------------------------------------------------- explorer */
/* The Projects window is a file manager over site/projects/. Chrome stacks
   toolbar, address bar, pane, status bar; only the pane scrolls, so the path
   and the object count stay put no matter how far down the folder you are. */

/* Rows are implicit so the same chrome works with or without an address bar —
   the bin has no path to type, so it has one row fewer. */
.win--explorer .win__body {
  padding: var(--space-2);
  display: grid;
  align-content: start;
  gap: var(--space-3);
}
.explorer__pane {
  height: 302px;
  overflow: auto;
  background: var(--surface-card);
  border: 1px solid var(--border-frame);
  box-shadow: var(--bevel-inset-thin);
}
.win--explorer:is(.is-max, .is-sized) .win__body {
  grid-template-rows: auto auto 1fr;
  overflow: hidden;
}
.win--bin:is(.is-max, .is-sized) .win__body { grid-template-rows: auto 1fr; }
.win--explorer:is(.is-max, .is-sized) .explorer__pane { height: auto; min-height: 0; }

.explorer__msg {
  padding: var(--space-6) var(--space-5);
  font: var(--type-ui);
  color: var(--text-muted);
  background-image: var(--texture-hatch);
}
.explorer__msg strong { display: block; color: var(--text-body); margin-bottom: var(--space-3); }
.explorer__msg code {
  font: var(--weight-normal) var(--text-xs)/1.4 var(--font-ui);
  background: var(--surface-input);
  border: 1px solid var(--border-hairline);
  padding: 0 3px;
}
.explorer__msg--fail strong { color: var(--brick-500); }

/* Icons view: glyph over label, the label alone carries selection. Columns are
   only as wide as a tile, so a stretched first column cannot push the leftmost
   glyph away from the edge — the gutter is the padding and nothing else. */
.explorer__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, 92px);
  justify-content: start;
  align-content: start;
  gap: var(--space-5) var(--space-2);
  padding: var(--space-5) var(--space-3);
}
.explorer__tile {
  display: grid;
  justify-items: center;
  gap: var(--space-3);
  padding: var(--space-2);
  background: none;
  border: none;
  cursor: pointer;
  font: var(--type-caption);
  color: var(--text-body);
  text-align: center;
  user-select: none;   /* the second click of a double-click must not select the name */
  -webkit-user-select: none;
  outline-offset: var(--focus-offset);
}
/* A tile keeps its glyph colour when selected — only the name inverts — so a
   folder stays amber here, as it is on the desktop. */
.explorer__tile .icon { --ic-color: var(--teal-700); }
.explorer__tile[data-folder] .icon { --ic-color: var(--amber-500); }
.explorer__tile__name {
  padding: 0 2px;
  overflow-wrap: anywhere;
}
.explorer__tile:hover .explorer__tile__name,
.explorer__tile[aria-selected="true"] .explorer__tile__name {
  background: var(--surface-selected);
  color: var(--text-titlebar);
}

/* Details view: four sortable columns over the shared list row. */
.explorer__head {
  position: sticky;
  top: 0;
  z-index: 1;
  display: grid;
  background: var(--surface-panel);
}
.explorer__head, .explorer__row { grid-template-columns: 1fr 60px 92px 116px; }
.explorer__col {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: 3px var(--space-4);
  font: var(--type-caption);
  color: var(--text-body);
  text-align: left;
  background: var(--surface-panel);
  border: 1px solid var(--border-frame);
  border-left-width: 0;
  box-shadow: var(--bevel-outset-thin);
  cursor: pointer;
  white-space: nowrap;
  outline-offset: var(--focus-offset);
}
.explorer__col:first-child { border-left-width: 1px; }
.explorer__col:hover { background: var(--parchment-200); }
.explorer__col:active { box-shadow: var(--bevel-inset-thin); }
.explorer__col__dir { flex: 1; text-align: right; color: var(--text-muted); font-size: var(--text-2xs); }

.explorer__row {
  display: grid;
  align-items: center;
  gap: 0;
  min-height: 0;
  padding: 3px var(--space-2);
  font: var(--type-caption);
}
.explorer__cell {
  padding: 0 var(--space-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.explorer__cell--name { display: flex; align-items: center; gap: var(--space-4); min-width: 0; }
.explorer__cell--name .icon { --ic-color: var(--teal-700); }
.explorer__row[data-folder] .icon { --ic-color: var(--amber-500); }
/* Restated after the folder tint so a lit row still lifts its glyph off the
   highlight — the two rules carry equal weight, so order decides. */
.explorer__row:hover .icon,
.explorer__row[aria-selected="true"] .icon { --ic-color: var(--parchment-50); }
.explorer__cell--name span { overflow: hidden; text-overflow: ellipsis; }
.explorer__cell--num { text-align: right; }
.explorer__row .explorer__cell--num,
.explorer__row .explorer__cell--muted { color: var(--text-muted); }
.explorer__row:hover .explorer__cell--num,
.explorer__row:hover .explorer__cell--muted,
.explorer__row[aria-selected="true"] .explorer__cell--num,
.explorer__row[aria-selected="true"] .explorer__cell--muted { color: var(--teal-100); }

/* ------------------------------------------------------------ recycle bin */
/* The bin borrows the explorer wholesale and only differs in depth: one object
   needs less pane than a folder does. */

.win--bin .explorer__pane { height: 196px; }

/* An application icon carries its own colour, so it is a real image rather
   than a tinted mask — and it stays hard-edged when scaled, like everything
   else here. */
.explorer__img {
  display: block;
  flex: 0 0 auto;
  image-rendering: pixelated;
}
.explorer__tile .explorer__img { width: 26px; height: 26px; }
.explorer__cell--name .explorer__img { width: 16px; height: 16px; }

/* -------------------------------------------------------- markdown reader */
/* Opening a file opens a window on it, the way a shell does — one per file,
   each with its own task bar button. The body is the .md rendered, nothing
   more; the status bar carries the path it came from. */

.win--doc .win__body { padding: 0; }
.md {
  padding: var(--space-6);
  background: var(--surface-card);
  border: 1px solid var(--border-frame);
  box-shadow: var(--bevel-inset-thin);
  margin: var(--space-2);
  max-height: min(400px, 52vh);
  overflow: auto;
}
.win--doc:is(.is-max, .is-sized) .win__body { display: flex; }
.win--doc:is(.is-max, .is-sized) .md { max-height: none; height: 100%; flex: 1; min-height: 0; }

.md > * + * { margin-top: var(--space-5); }
.md h1 {
  font: var(--type-title);
  color: var(--ink);
  padding-bottom: var(--space-4);
  border-bottom: 1px solid var(--border-hairline);
}
.md h2 {
  font: var(--weight-bold) var(--text-md)/var(--leading-ui) var(--font-ui);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--teal-700);
  margin-top: var(--space-7);
}
.md h3 { font: var(--weight-bold) var(--text-sm)/var(--leading-ui) var(--font-ui); margin-top: var(--space-6); }
.md h4 { font: var(--weight-bold) var(--text-sm)/var(--leading-ui) var(--font-ui); color: var(--text-muted); }
.md p { font: var(--type-body); }
/* Markers are drawn in the gutter rather than as a flex sibling: an item that
   opens with a bold run would otherwise break into two columns. */
.md ul, .md ol { display: grid; gap: var(--space-3); padding-left: 0; }
.md li { position: relative; padding-left: var(--space-7); font: var(--type-ui); line-height: var(--leading-body); }
.md li::before { position: absolute; left: 0; top: 0; color: var(--teal-500); }
.md ul li::before { content: "\25A0"; left: 3px; font-size: var(--text-2xs); line-height: 1.7; }
.md ol { counter-reset: md-item; }
.md ol li { counter-increment: md-item; }
.md ol li::before {
  content: counter(md-item) ".";
  color: var(--teal-600);
  font: var(--weight-bold) var(--text-xs)/var(--leading-body) var(--font-ui);
}
.md code {
  font: var(--weight-normal) var(--text-xs)/1.4 var(--font-ui);
  background: var(--surface-input);
  border: 1px solid var(--border-hairline);
  padding: 0 3px;
}
/* The face goes on the block, not on the code inside it: a bare <pre> carries
   the browser's own monospace default, which is the one font on the page that
   answers to nothing in the design system. */
.md pre {
  margin: 0;
  padding: var(--space-5);
  overflow-x: auto;
  font: var(--weight-normal) var(--text-sm)/1.5 var(--font-terminal);
  background: var(--surface-statusbar);
  border: 1px solid var(--border-frame);
  box-shadow: var(--bevel-inset-thin);
}
.md pre code {
  font: inherit;
  color: var(--green-400);
  background: none;
  border: none;
  padding: 0;
}
.md blockquote {
  margin: 0;
  padding: var(--space-4) var(--space-5);
  border-left: 3px solid var(--teal-500);
  background: var(--surface-well);
  font: var(--type-ui);
  line-height: var(--leading-body);
  color: var(--text-muted);
}
.md hr { height: 1px; margin: var(--space-6) 0; border: none; background: var(--border-hairline); box-shadow: 0 1px 0 var(--chrome-light); }
/* A screenshot in a project note. It sits in a well the way a thumbnail does,
   and it is a control rather than a picture: pressing it hands the file to the
   viewer, so it gets the bevel, the hover and the 1px press of one. Rendered
   smoothly, never nearest-neighbour — a screenshot is being scaled down here,
   and hard pixels on a downscale read as damage rather than as pixel art, the
   same exception the portrait and the fitted viewer make. */
.md__figure { display: grid; gap: var(--space-3); margin: 0; }
.md__shot {
  display: block;
  padding: var(--space-2);
  background: var(--surface-well);
  border: 1px solid var(--border-frame);
  box-shadow: var(--bevel-outset-thin);
  cursor: pointer;
  outline-offset: var(--focus-offset);
}
.md__shot:hover { background: var(--parchment-200); }
.md__shot:active { box-shadow: var(--bevel-inset-thin); padding: calc(var(--space-2) + 1px) var(--space-2) calc(var(--space-2) - 1px); }
.md__shot img { display: block; width: 100%; height: auto; image-rendering: auto; }
.md__figure figcaption {
  font: var(--type-caption);
  color: var(--text-muted);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}

.md a { color: var(--text-link); }
.md a:visited { color: var(--text-link-visited); }
.md strong { font-weight: var(--weight-bold); }

.footer-caps {
  margin-top: var(--space-5);
  padding-top: var(--space-4);
  border-top: 1px solid var(--border-hairline);
  font: var(--type-2xs);
  font-size: var(--text-2xs);
  font-family: var(--font-ui);
  color: var(--text-muted);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  text-align: center;
}

/* ------------------------------------------------------------ music player */
/* player.exe. A lit screen, a block meter, four transport keys, a volume
   slider and the folder as a list. The screen is the one thing here that is
   not parchment: it is the status bar's teal with the terminal face on it, the
   way a hi-fi front panel was the one lit part of a beige machine. */

.player { display: grid; gap: var(--space-5); }

.player__screen {
  padding: var(--space-4) var(--space-5);
  background: var(--surface-statusbar);
  background-image: var(--texture-scanline);
  border: 1px solid var(--border-frame);
  box-shadow: var(--bevel-inset-thin);
}
.player__now { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-4); }
.player__title {
  min-width: 0;
  font: var(--type-terminal);
  color: var(--green-400);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.player__time {
  flex: 0 0 auto;
  font: var(--type-terminal);
  color: var(--green-400);
  font-variant-numeric: tabular-nums;
}
.player__state {
  margin-top: 2px;
  font: var(--type-caption);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--teal-300);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
/* A folder that will not read says so on the screen itself, in the colour the
   alert cards use — there is nowhere else on this window to put it. */
.player__state--fail { color: var(--brick-500); }

.player__transport { display: flex; align-items: center; gap: var(--space-4); }
.player__keys { display: flex; gap: 2px; }
.player__key {
  width: 30px; height: 30px;
  display: grid;
  place-items: center;
  padding: 0;
  background: var(--surface-panel);
  border: 1px solid var(--border-frame);
  box-shadow: var(--bevel-outset);
  cursor: pointer;
  outline-offset: var(--focus-offset);
}
.player__key:hover { background: var(--parchment-200); }
/* Pressing flips the bevel and nudges the glyph a pixel with it, so the key
   looks as though it went in rather than merely changing colour. Play holds
   that state while it is playing: it is a latch, not a momentary. */
.player__key:active,
.player__key[aria-pressed="true"] { box-shadow: var(--bevel-inset); }
.player__key:active .pixglyph,
.player__key[aria-pressed="true"] .pixglyph { transform: translate(1px, 1px); }
.player__key:disabled { cursor: default; box-shadow: var(--bevel-outset-thin); }
.player__key:disabled .pixglyph { fill: var(--text-disabled); }

.pixglyph { width: 12px; height: 12px; display: block; fill: var(--teal-800); }
/* Play and pause live in the same key; the class on the key says which shows. */
.player__key .pixglyph--pause { display: none; }
.player__key[aria-pressed="true"] .pixglyph--play { display: none; }
.player__key[aria-pressed="true"] .pixglyph--pause { display: block; }

.player__list {
  height: 136px;
  overflow: auto;
  background: var(--surface-card);
  border: 1px solid var(--border-frame);
  box-shadow: var(--bevel-inset-thin);
}
/* The track playing is held lit the way a selected row is — the list is the
   playlist, so "selected" and "playing" are the same thing here. */
.player__row .player__row__meta {
  flex: 0 0 auto;
  font: var(--type-caption);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.player__row__name { flex: 1; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.player__row:hover .player__row__meta,
.player__row[aria-selected="true"] .player__row__meta { color: var(--teal-100); }
/* The row for the current track keeps its glyph — every other row has an empty
   box the same size, so the names stay in one column. */
.player__row__gap { flex: 0 0 auto; width: 16px; }

/* ------------------------------------------------------------ photo viewer */
/* viewer.exe. The explorer's toolbar over a dithered well, a thumbnail strip
   and a status line. Everything but the chrome comes from site/photos/. */

.viewer { display: grid; gap: var(--space-4); align-content: start; }

.viewer__stage {
  height: 190px;
  display: grid;
  place-items: center;
  overflow: auto;
  background: var(--surface-input);
  background-image: var(--texture-dither);
  border: 1px solid var(--border-frame);
  box-shadow: var(--bevel-inset-thin);
}
/* Fitted, a photo is being scaled down, and nearest-neighbour on a downscale
   reads as damage rather than as pixel art — so it opts out of the global
   pixelated rendering, as the portrait in about_me.txt does. Magnified it opts
   back in: blowing an image up in hard squares is what a viewer of this era
   did, and it is the only honest way to show what a pixel actually is. */
.viewer__img {
  display: block;
  max-width: 100%;
  max-height: 186px;
  image-rendering: auto;
}
.viewer__img.is-zoomed { max-width: none; max-height: none; image-rendering: pixelated; }
/* Given a stage of its own, a fitted photo is bounded by that stage rather than
   by the 186px it is drawn at — it may fill a maximised window and must still
   fit a small one. Magnified it stays unbounded: overflowing the stage is what
   magnifying is for, and the stage scrolls. */
.win--viewer:is(.is-max, .is-sized) .viewer__img:not(.is-zoomed) { max-height: 100%; }

.viewer__strip {
  display: flex;
  gap: var(--space-2);
  overflow-x: auto;
  padding-bottom: var(--space-1);
}
/* The 2px of padding is what makes the selection legible: an image set to cover
   its button would hide any background put behind it, so the button keeps a
   margin of itself showing all the way round and that margin is what lights. */
.viewer__thumb {
  flex: 0 0 auto;
  width: 46px; height: 40px;
  display: grid;
  place-items: center;
  padding: 2px;
  overflow: hidden;
  background: var(--surface-input);
  background-image: var(--texture-dither);
  border: 1px solid var(--border-frame);
  box-shadow: var(--bevel-inset-thin);
  cursor: pointer;
  outline-offset: var(--focus-offset);
}
/* The current thumbnail is a raised control on the OS's own teal, so which one
   is showing reads from across the window. */
.viewer__thumb[aria-selected="true"] {
  background: var(--teal-800);
  background-image: var(--texture-scanline);
  border-color: var(--teal-800);
  box-shadow: var(--bevel-outset);
}
.viewer__thumb img { display: block; width: 100%; height: 100%; object-fit: cover; image-rendering: auto; }
/* Everything but the current one is held back a little, so the strip reads as
   one thing lit rather than five things competing. */
.viewer__thumb img { opacity: 0.72; }
.viewer__thumb:hover img,
.viewer__thumb[aria-selected="true"] img { opacity: 1; }

.viewer__foot {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border-frame);
  font: var(--type-caption);
  color: var(--text-muted);
}
.viewer__foot__path { min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.viewer__foot__meta { flex: 0 0 auto; font-variant-numeric: tabular-nums; }

/* Maximised, the stage takes the slack and the rest keeps its height — the
   same bargain the explorer's pane makes. */
.win--viewer:is(.is-max, .is-sized) .win__body { display: grid; overflow: hidden; }
.win--viewer:is(.is-max, .is-sized) .viewer { grid-template-rows: auto 1fr auto auto; min-height: 0; }
/* The row is pinned to the stage's own height rather than left to size itself
   from what is in it. Otherwise the photo stretches the row and then measures
   its own max-height against the row it just stretched, which bounds nothing. */
.win--viewer:is(.is-max, .is-sized) .viewer__stage {
  height: auto;
  min-height: 0;
  grid-template-rows: 100%;
}

/* A folder that will not read fills the stage with the explorer's message. */
.viewer__stage .explorer__msg { background-image: none; }

/* ----------------------------------------------------------------- terminal */
/* A shell on the drive the explorer walks. The screen is the player's — the
   status bar's teal, scanlined, with the terminal face on it — which makes
   three places on this page allowed that face, and this is the one it was cut
   for. The prompt is the last line of the log rather than a bar under it,
   because that is where a terminal keeps it. */

.win--terminal .win__body { padding: var(--space-4); }
.term {
  /* Twenty-four rows of the 20px line the terminal face sets — the other half
     of the size a terminal has always been, and what neofetch's block needs to
     land in one screenful. */
  height: 480px;
  overflow-y: auto;
  padding: var(--space-4) var(--space-5);
  background: var(--surface-statusbar);
  background-image: var(--texture-scanline);
  border: 1px solid var(--border-frame);
  box-shadow: var(--bevel-inset-thin);
  font: var(--type-terminal);
  color: var(--green-400);
  cursor: text;   /* the whole screen is the prompt */
}
.win--terminal:is(.is-max, .is-sized) .win__body { display: flex; }
.win--terminal:is(.is-max, .is-sized) .term { height: auto; flex: 1; min-height: 0; }

/* A row is one line of a character grid: its spaces are load-bearing, which is
   what lets art be written as text. A line too long for the window wraps rather
   than pushing the screen sideways — the same thing a terminal does. */
.term__row {
  min-height: 1.25em;   /* an empty line is still a line */
  white-space: pre-wrap;
  overflow-wrap: break-word;
}

.term__line { display: flex; align-items: baseline; }
.term__line[hidden] { display: none; }   /* a running command owns the line */
.term__prompt { white-space: pre; cursor: text; }
.term__input {
  flex: 1;
  min-width: 0;
  padding: 0;
  border: none;
  background: none;
  font: inherit;
  color: var(--parchment-200);
  caret-color: var(--green-400);
  /* No focus ring: the caret is already the one thing on a screen like this
     saying where what you type is going. */
  outline: none;
}

/* This is the alert card's ground, so the red is the card's red — the same hue
   re-pitched for a dark panel, at 3.95:1. The palette's --status-danger is
   drawn for parchment and falls to 2.3:1 here, which is the whole reason that
   value exists. Everything else is a token: amber 6.6:1, green 8:1, teal-300
   4.4:1, blue-400 4.3:1. */
.term__who,
.term__key,
.term__prize { color: var(--amber-500); }
.term__where { color: var(--blue-400); }
/* A folder in a listing wears the amber every folder on this desktop wears, so
   ls needs no marks of its own to say which names are places. */
.term__dir { color: var(--amber-500); }
.term__punct,
.term__echo { color: var(--parchment-200); }
.term__brand { color: var(--parchment-50); }
.term__art { color: var(--teal-100); }
.term__dim { color: var(--teal-300); }
.term__fail { color: #ef5340; }

/* The colour bar a fetch script signs off with, drawn as boxes rather than set
   as characters: a full block is not in every font that might stand in for
   VT323, and eight tofu boxes is a worse joke than none. */
.term__bar { display: flex; }
.term__sw { width: 3ch; height: 0.9em; }
.term__sw--1 { background: var(--brick-500); }
.term__sw--2 { background: var(--amber-500); }
.term__sw--3 { background: var(--green-500); }
.term__sw--4 { background: var(--teal-500); }
.term__sw--5 { background: var(--blue-600); }
.term__sw--6 { background: var(--blue-400); }
.term__sw--7 { background: var(--teal-100); }
.term__sw--8 { background: var(--parchment-50); }

/* --------------------------------------------------------------- responsive */
/* Below the desktop breakpoint the window-placement half of the metaphor stops
   being usable: windows stack in a single scrolling column, chrome stays
   intact, moving and resizing a window is off. Icons keep their drag — it
   reorders them within the column instead of free-placing them on a grid. */

@media (max-width: 900px) {
  /* No letterbox and no free-placed icons: the column is the layout. */
  .desktop { width: 100%; height: 100dvh; }
  .desktop__surface { padding: var(--space-5); overflow-y: auto; }
  .desk-icons {
    position: static;
    inset: auto;
    pointer-events: auto;
    width: auto;
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-4);
    justify-content: center;
    margin-bottom: var(--space-6);
  }
  .desk-icon { position: static; }
  /* The column is the width, so a window's own minimum stops applying — a
     440px floor on a 360px phone would only push the page sideways. */
  .desktop__surface > .win {
    position: static;
    width: 100% !important;
    max-width: 560px;
    height: auto !important;
    min-width: 0 !important;
    min-height: 0 !important;
    max-height: none !important;
    margin: 0 auto var(--space-6);
  }
  .desktop__surface > .win.is-max { box-shadow: var(--shadow-window); }
  .desktop__surface > .win > .win__grab { display: none; }
  .win__bar { cursor: default; }
  .win__scroll { max-height: none; }
  /* The pane keeps a fixed height even in the stacked column: a folder that
     grows without bound would push the status bar off the fold. */
  .explorer__pane { height: 260px; }
  .explorer__head, .explorer__row { grid-template-columns: 1fr 56px 84px; }
  .explorer__col:nth-child(4), .explorer__cell--when { display: none; }
  .md { max-height: 420px; }
  .toast { left: var(--space-5); right: var(--space-5); justify-content: center; }
  /* Window buttons stop being legible once they are 60px wide — Start covers it. */
  .taskbar__items { display: none; }
}

@media (max-width: 480px) {
  .kv { grid-template-columns: 1fr; gap: var(--space-1) 0; }
  .kv dd { margin-bottom: var(--space-3); }
  .job__head, .job__role { flex-direction: column; align-items: flex-start; gap: 0; }
  .job__where { text-align: left; }
}

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

/* --------------------------------------------------------------- printing */
/* Print / Save PDF hands over the PDF itself, so this is for the visitor who
   prints the page instead: the resume alone, no chrome, all tabs expanded. */

.print-head { display: none; }

@media print {
  body { display: block; overflow: visible; background: none; }
  .desktop { display: block; width: auto; height: auto; background: none; }
  .desktop__surface { overflow: visible; padding: 0; }

  .win { display: none !important; }
  .desk-icons, .taskbar, .startmenu, .toast, .scrim { display: none !important; }

  #win-resume {
    display: block !important;
    position: static !important;
    width: 100% !important;
    max-width: none !important;   /* beats the narrow-viewport column cap */
    height: auto !important;
    margin: 0;
    border: none;
    box-shadow: none;
    background: none;
  }
  #win-resume .win__bar,
  #win-resume .win__footer,
  #win-resume .tabs__strip { display: none !important; }
  #win-resume .win__body { padding: 0; max-height: none; overflow: visible; }
  #win-resume .tabs__body { padding: 0; border: none; box-shadow: none; background: none; }
  #win-resume .tabs__panel {
    display: grid !important;
    margin-bottom: var(--space-8);
    break-inside: avoid;
  }
  #win-resume article { break-inside: avoid; }
  #win-resume .chip { box-shadow: none; }

  .print-head {
    display: block;
    margin-bottom: var(--space-6);
    padding-bottom: var(--space-4);
    border-bottom: 1px solid var(--border-frame);
  }
  .print-head strong { display: block; font: var(--weight-bold) var(--text-2xl)/1.2 var(--font-ui); }
  .print-head span { font: var(--type-ui); color: var(--text-muted); }
}
