/* ==========================================================================
   TineSH — modern dark minimalism

   Ported from the shadcn/ui default "neutral" token set (post-March-2025 dark
   values, Tailwind v4 / OKLCH). Values are verbatim from ui.shadcn.com/docs/theming:

     dark   background oklch(.145 0 0)  card oklch(.205 0 0)  muted oklch(.269 0 0)
            foreground oklch(.985 0 0)  muted-fg oklch(.708 0 0)  ring oklch(.556 0 0)
            border oklch(1 0 0 / 10%)   input oklch(1 0 0 / 15%)
     light  background oklch(1 0 0)     card oklch(1 0 0)     muted oklch(.97 0 0)
            foreground oklch(.145 0 0)  muted-fg oklch(.556 0 0)  border oklch(.922 0 0)

   Three things that define the register, all of them counter-intuitive:

   1. The card/background step is deliberately TINY — 1.10:1. Surfaces are
      separated by a 10%-white hairline, not by fill. Raising the fill to make
      cards "pop" is the single most common way this style gets faked.
   2. Neutrals are true gray (chroma 0). No blue/zinc tint.
   3. In dark mode borders are LIGHTER than the surface; in light mode darker.
      Same token, inverted relationship — not a colour inversion.

   Hierarchy is carried by type scale, weight and the two-tier foreground
   (foreground / muted-foreground). No accent colour is introduced: the lime in
   the logo is the only chroma on the page, which is the point.

   ONLY this file changed. index.html, links.js and rcs.js are untouched, so every
   selector targets markup that already exists. Notes on that constraint:
   - rcs.js sets/removes data-theme="dark" only. The page is dark by default now,
     so :root holds dark and [data-theme] holds light. The toggle still works and
     still persists; only the attribute name reads backwards.
   - Tailwind's CDN <style> is injected AFTER this file, so its preflight and any
     utility in the HTML wins at equal specificity. Where that matters the
     selector here is deliberately more specific (.profile h1, .section-label.socials-label).
   - Only Space Grotesk 700/800/900 and DM Mono 400/500 are loaded, so the sans
     has no light weights available. Type hierarchy uses size + the mono/sans
     split rather than weight ramps.
   ========================================================================== */

:root {
  color-scheme : dark;

  /* Hex first, OKLCH second: browsers without oklch() keep the hex. */
  --background       : #0a0a0a;
  --background       : oklch(.145 0 0);
  --foreground       : #fafafa;
  --foreground       : oklch(.985 0 0);

  --card             : #171717;
  --card             : oklch(.205 0 0);
  --card-elevated    : #262626;
  --card-elevated    : oklch(.269 0 0);

  --muted            : #262626;
  --muted            : oklch(.269 0 0);
  /* Icon and affordance tones. Explicit COLOURS, not opacity: opacity
     composites toward the background, so one value that works on near-black
     drops the arrow to 1.8:1 the moment the theme flips to white.
       --icon      6.5:1 dark / 4.7:1 light  (semantic, sits forward)
       --icon-dim  4.3:1 dark / 3.8:1 light  (affordance, recedes) */
  --muted-foreground : #a1a1a1;
  --muted-foreground : oklch(.708 0 0);
  --text-muted       : #b5b5b5;
  --text-muted       : oklch(.769 0 0);
  --icon             : #949494;
  --icon             : oklch(.665 0 0);
  --icon-dim         : #757575;
  --icon-dim         : oklch(.565 0 0);

  --border           : rgb(255 255 255 / 10%);
  --border           : oklch(1 0 0 / 10%);
  --input            : rgb(255 255 255 / 15%);
  --input            : oklch(1 0 0 / 15%);
  --ring             : #737373;
  --ring             : oklch(.556 0 0);

  /* radius scale, all derived from one value — shadcn's --radius: .625rem */
  --radius           : .625rem;
  --radius-sm        : calc(var(--radius) - 4px);
  --radius-md        : calc(var(--radius) - 2px);
  --radius-lg        : var(--radius);
  --radius-xl        : calc(var(--radius) + 4px);

  /* rcs.js writes these two inline during the theme-toggle nudge, so the names
     have to survive. Whisper-level: depth here comes from the hairline. */
  --shadow           : 0 1px 2px 0 rgb(0 0 0 / 30%);
  --shadow-lg        : 0 1px 2px 0 rgb(0 0 0 / 30%), 0 8px 24px -12px rgb(0 0 0 / 60%);

  --ease             : cubic-bezier(.16, 1, .3, 1);
  --ease-soft        : cubic-bezier(.4, 0, .2, 1);

  /* index.html loads Space Grotesk at 700/800/900 only — no regular weights —
     so it can only serve display type. UI text uses the platform neo-grotesque
     (SF / Segoe UI Variable / Roboto), which is what this register actually
     uses: Geist and Inter are both neo-grotesques, never geometric sans. */
  --font-display     : 'Space Grotesk', ui-sans-serif, system-ui, sans-serif;
  --font-ui          : -apple-system, BlinkMacSystemFont, 'Segoe UI Variable Text',
                       'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  --font-mono        : 'DM Mono', ui-monospace, SFMono-Regular, Menlo, monospace;

  /* The single place the brand colour appears in the UI: the focus ring.
     Spending it there makes it a decision rather than an inherited asset
     colour, and focus is what most needs to be unmissable on a page of links. */
  --accent           : #a3e635;

  /* arrow-up-right (lucide), inlined as a mask so the row's ::after can render
     an outbound-link glyph without touching the markup. */
  --icon-arrow       : url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7 7h10v10'/%3E%3Cpath d='M7 17 17 7'/%3E%3C/svg%3E");
  /* lucide `send` — the mailto: row's affordance, since it doesn't open a page. */
  --icon-mail-arrow  : url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m22 2-7 20-4-9-9-4Z'/%3E%3Cpath d='M22 2 11 13'/%3E%3C/svg%3E");
}

[data-theme="dark"] {
  color-scheme : light;

  --background       : #ffffff;
  --background       : oklch(1 0 0);
  --foreground       : #0a0a0a;
  --foreground       : oklch(.145 0 0);

  --card             : #ffffff;
  --card             : oklch(1 0 0);
  --card-elevated    : #f5f5f5;
  --card-elevated    : oklch(.97 0 0);

  --muted            : #f5f5f5;
  --muted            : oklch(.97 0 0);
  --muted-foreground : #737373;
  --muted-foreground : oklch(.556 0 0);
  --text-muted       : #666666;
  --text-muted       : oklch(.505 0 0);
  --icon             : #737373;
  --icon             : oklch(.556 0 0);
  --icon-dim         : #828282;
  --icon-dim         : oklch(.61 0 0);

  /* Not shadcn's oklch(.922) — measured, a 10% white hairline on near-black
     lands at 1.24:1, while #e5e5e5 on white is 1.26:1 but *perceptually* much
     weaker, because light strokes bloom on dark ground and dark ones don't on
     light. The light border is stepped down to compensate. */
  --border           : #dcdcdc;
  --border           : oklch(.888 0 0);
  --input            : #d4d4d4;
  --input            : oklch(.865 0 0);
  --ring             : #a1a1a1;
  --ring             : oklch(.708 0 0);

  --shadow           : 0 1px 2px 0 rgb(10 10 10 / 5%);
  --shadow-lg        : 0 1px 2px 0 rgb(10 10 10 / 5%), 0 8px 24px -12px rgb(10 10 10 / 12%);

  /* lime-400 is ~1.6:1 on white, so light mode drops to lime-700. */
  --accent           : #4d7c0f;
}

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

/* Vertically centred rather than top-anchored, so a short page doesn't leave a
   void under the footer. Grid, not flex: index.html has #theme-btn as a SIBLING
   of .wrapper, and putting both in the same grid cell lets the toggle align to
   the top-right of the content column itself instead of the window corner. */
body {
  font-family             : var(--font-ui);
  font-size               : 14px;
  line-height             : 1.5;
  background-color        : var(--background);
  color                   : var(--foreground);
  min-height              : 100vh;
  min-height              : 100svh;
  display                 : grid;
  /* An explicit track, not `auto`: a percentage width inside an auto track
     resolves against content, which collapses the column to max-content. */
  grid-template-columns   : min(100%, 28rem);
  justify-content         : center;
  align-content           : center;
  padding                 : 4rem 1.5rem;
  -webkit-font-smoothing  : antialiased;
  -moz-osx-font-smoothing : grayscale;
  transition              : background-color .2s var(--ease-soft),
                            color .2s var(--ease-soft);
}

::selection {
  background : var(--foreground);
  color      : var(--background);
}

/* Focus is the one place a visible ring is non-negotiable — and the one place
   the brand lime is spent. */
:focus-visible {
  outline        : 2px solid var(--accent);
  outline-offset : 2px;
  border-radius  : var(--radius-sm);
}

/* --------------------------------------------------------------- toggle --- */

/* shadcn's ghost icon button. Not fixed: it's a grid sibling sharing .wrapper's
   cell, so `justify-self:end` puts it on the column's right rail and it scrolls
   with the content instead of floating over it. The negative margin pulls the
   36px box's padding off the rail so the GLYPH aligns with the row arrows. */
#theme-btn {
  position        : relative;
  justify-self    : end;
  align-self      : start;
  margin          : -8px -11px 0 0;
  z-index         : 10;
  width           : 36px;
  height          : 36px;
  display         : flex;
  align-items     : center;
  justify-content : center;
  border          : 0;
  border-radius   : var(--radius-md);
  background      : transparent;
  box-shadow      : none;
  color           : var(--icon);
  cursor          : pointer;
  transition      : color .15s var(--ease-soft),
                    background-color .15s var(--ease-soft);
}

#theme-btn:hover {
  color      : var(--foreground);
  background : var(--muted);
  transform  : none;
  box-shadow : none;
}

#theme-btn:active {
  transform  : none;
  box-shadow : none;
}

/* Beats the w-[20px] utility in the HTML and the inline size rcs.js rewrites.
   14px, same as every other glyph on the page. */
#theme-btn svg {
  width  : 14px !important;
  height : 14px !important;
}

/* --------------------------------------------------------------- layout --- */

/* Both grid children occupy the same cell, so the toggle can be positioned
   against the content column's own box rather than the viewport. */
.wrapper,
#theme-btn {
  grid-area : 1 / 1;
}

.wrapper {
  position  : relative;
  width     : 100%;
  max-width : 28rem;
  margin    : 0 auto;
}

/* The toggle is absolutely positioned against .wrapper, and the avatar is 44px
   against its 36px, so this pads the identity block enough to sit them on a
   shared optical centre. */
.profile {
  text-align    : left;
  margin-bottom : 2rem;
  animation     : rise .5s var(--ease) both;
}

/* logo.png has a white plate baked around the lime tile — on a near-black page
   that reads as a bright square. The tile's ink spans 926 of the file's 1254px,
   centred, so scaling 1254/926 ≈ 1.354 inside a clipped box crops the plate
   away. The tile's own dark outline then serves as the edge. */
.avatar-wrap {
  display       : block;
  position      : relative;
  width         : 44px;
  height        : 44px;
  margin-bottom : 1.25rem;
  border-radius : var(--radius-md);
  overflow      : hidden;
  line-height   : 0;
}

.avatar-wrap::after {
  content : none;
}

.avatar-wrap img {
  width         : 100%;
  height        : 100%;
  border        : 0;
  border-radius : 0;
  object-fit    : cover;
  transform     : scale(1.354);
}

/* .profile h1 — Tailwind's preflight injects h1{font-size:inherit} after this
   file, so a bare `h1` selector ties and loses. */
.profile h1 {
  font-family    : var(--font-display);
  font-size      : 1.75rem;
  font-weight    : 700;
  letter-spacing : -.04em;
  line-height    : 1.15;
  text-transform : none;
  color          : var(--foreground);
}

/* The orange highlight block is gone; scale and weight carry the emphasis. */
.profile h1 .highlight {
  display     : inline;
  background  : none;
  color       : inherit;
  padding     : 0;
  margin      : 0;
  font-size   : inherit;
  font-weight : inherit;
}

/* Second foreground tier — the whole secondary hierarchy is this one token. */
.tagline {
  display        : block;
  margin-top     : .5rem;
  border         : 0;
  padding        : 0;
  background     : none;
  box-shadow     : none;
  color          : var(--text-muted);
  font-size      : .8125rem;
  font-weight    : 400;
  letter-spacing : 0;
  text-transform : none;
}

/* --------------------------------------------------------------- labels --- */

/* Section rhythm on one 8px scale: 32px between sections, 12px label→content. */
.section-label {
  display        : flex;
  align-items    : center;
  gap            : .75rem;
  font-family    : var(--font-mono);
  font-size      : .6875rem;
  font-weight    : 500;
  text-transform : uppercase;
  letter-spacing : .1em;
  opacity        : 1;
  color          : var(--text-muted);
  margin-top     : 0;
  margin-bottom  : .75rem;
}

/* No trailing rule on the labels. Total horizontal lines on the page: 4 row
   dividers + 1 footer rule = 5. A decorative rule per section label pushed that
   to 8 in a 448px column, which reads as over-ruled — the row dividers are the
   ones doing real work, so the ornamental pair goes. */
.section-label::after {
  content : none;
}

/* Two classes, to outrank the mt-[2.2rem] utility sitting on this element. */
.section-label.socials-label {
  text-align : left;
  margin-top : 2rem !important;
}

/* ---------------------------------------------------------------- links --- */

/* One left axis for the whole page. The rows have no horizontal padding, so the
   icon column lands on the same 416px spine as the h1, labels and footer.
   Dividers between rows only — no top or bottom border on the group, because the
   list's extent is already legible from the label above and the gap below. */
#links-container {
  display        : flex;
  flex-direction : column;
  gap            : 0;
  border         : 0;
  border-radius  : 0;
  background     : transparent;
}

.link-card {
  display         : flex;
  align-items     : center;
  justify-content : space-between;
  gap             : .75rem;
  padding         : .625rem 0;
  min-height      : 40px;
  background      : transparent;
  border          : 0;
  border-top      : 1px solid var(--border);
  border-radius   : 0;
  box-shadow      : none;
  text-decoration : none;
  color           : var(--foreground);
  /* Row labels are UI text at weight 500, not display type. Space Grotesk is
     only loaded at 700+, so using it here would force every label bold — which
     flattens hierarchy and is the loudest tell that this isn't the real thing. */
  font-family     : var(--font-ui);
  font-weight     : 500;
  font-size       : .875rem;
  line-height     : 1.25rem;
  letter-spacing  : -.006em;
  opacity         : 0;
  animation       : rise .45s var(--ease) both;   /* delay comes from rcs.js */
  transition      : color .15s var(--ease-soft);
}

.link-card:first-child {
  border-top : 0;
}

/* With no fill to change, hover moves the text and glyph tone instead — the
   quietest possible state change, and the one Linear's lists actually use. */
.link-card:hover {
  background   : transparent;
  border-color : var(--border);
  transform    : none;
  box-shadow   : none;
}

.link-card:active {
  background : transparent;
  transform  : none;
  box-shadow : none;
}

/* Focus needs to sit above the sibling rows or the ring gets clipped. */
.link-card:focus-visible {
  position       : relative;
  z-index        : 1;
  outline-offset : -2px;
}

/* The three colour-coded left rails are dropped; every row is equal weight. */
.link-card.accent-1,
.link-card.accent-2,
.link-card.accent-3 {
  border-left : 0;
}

.link-card.accent-1:hover,
.link-card.accent-2:hover,
.link-card.accent-3:hover {
  border-left : 0;
}

.link-left {
  display     : flex;
  align-items : center;
  gap         : .75rem;
  min-width   : 0;
}

/* Semantic icon sits forward of the affordance — 6.5:1 against the arrow's
   3.5:1. Explicit tokens, not opacity, so the relationship survives the theme
   flip (see the token block). */
.link-icon {
  width           : 14px;
  height          : 14px;
  flex            : 0 0 14px;
  display         : flex;
  align-items     : center;
  justify-content : center;
  border          : 0;
  border-radius   : 0;
  background      : none;
  color           : var(--icon);
  transition      : color .15s var(--ease-soft);
}

.link-card.accent-1 .link-icon,
.link-card.accent-2 .link-icon,
.link-card.accent-3 .link-icon {
  background : none;
  color      : var(--icon);
}

.link-card:hover .link-icon {
  color        : var(--foreground);
  border-color : transparent;
}

/* Equal contrast is not equal presence: the globe has internal meridians and
   reads heavy, `<>` is two thin strokes and reads light. Stroke width is tuned
   per glyph so the column of icons has one optical weight. */
.link-icon svg {
  width        : 14px;
  height       : 14px;
  stroke-width : 1.6;
}

/* Sparse glyphs — few strokes, lots of interior space — need more weight. */
.link-icon .lucide-code,
.link-icon .lucide-sparkle,
.link-icon .lucide-sparkles,
.social-btn .lucide-github {
  stroke-width : 1.9;
}

/* Dense glyphs — closed forms plus interior detail — need less. */
.link-icon .lucide-globe,
.link-icon .lucide-mail,
.link-icon .lucide-cloud-upload {
  stroke-width : 1.5;
}

/* Chevron-right means "drill into a hierarchy" — wrong for outbound links. The
   markup can't be changed, so the lucide glyph is hidden and an arrow-up-right
   is drawn as a mask on the row instead (mask, not background, so it inherits
   currentColor and both themes work). Revealed on intent, not decorating all
   five rows permanently. */
.arrow-icon {
  display : none;
}

.link-card::after {
  content         : '';
  flex            : none;
  margin-left     : auto;
  width           : 14px;
  height          : 14px;
  background      : currentColor;
  /* Persistent but dim, then brightened and nudged on intent. A binary
     hover-reveal leaves the right edge empty (rows stop reading as clickable)
     and strands touch users, who have no hover at all. Same 14px box as the
     leading icon — differentiated by tone, not by scale. */
  color           : var(--icon-dim);
  transform       : none;
  transition      : color .15s var(--ease-soft),
                    transform .2s var(--ease-soft);
  -webkit-mask    : var(--icon-arrow) center / contain no-repeat;
  mask            : var(--icon-arrow) center / contain no-repeat;
}

.link-card:hover::after,
.link-card:focus-visible::after {
  transform : translate(1px, -1px);
  color     : var(--foreground);
}

/* `mailto:` is not an outbound page — arrow-up-right would be a lie. The email
   row is the 2nd child, so :nth-child targets it without touching links.js. */
.link-card:nth-child(2)::after {
  -webkit-mask : var(--icon-mail-arrow) center / contain no-repeat;
  mask         : var(--icon-mail-arrow) center / contain no-repeat;
}

/* -------------------------------------------------------------- socials --- */

#socials-container {
  display         : flex;
  justify-content : flex-start;
  gap             : 1.25rem;
  flex-wrap       : wrap;
  padding         : .625rem 0;
  opacity         : 0;
  animation       : rise .45s .5s var(--ease) both;
}

/* Bare glyphs on the same spine as the row icons — no chips. Boxed buttons
   sitting 60px under borderless rows is two surface languages in one column,
   and these are the least important controls on the page.
   They are also the only UNLABELLED controls here — each glyph carries all of
   its own meaning — so they run 16px where the row icons (redundant with the
   label beside them) run 14px. Uniformity would cost comprehension. */
.social-btn {
  position        : relative;
  width           : 16px;
  height          : 16px;
  display         : flex;
  align-items     : center;
  justify-content : center;
  border          : 0;
  border-radius   : 0;
  background      : transparent;
  box-shadow      : none;
  text-decoration : none;
  color           : var(--icon);
  transition      : color .15s var(--ease-soft);
}

/* 44px tap target from an inset pseudo-element, so the glyph stays 16px and
   stays on the spine — a padded 32px button would overhang the rail. */
.social-btn::before,
#theme-btn::before {
  content  : '';
  position : absolute;
  inset    : -14px;
}

/* The toggle is a 36px box; -4px inset takes it to 44px without moving it. */
#theme-btn::before {
  inset : -4px;
}

.social-btn:hover {
  color        : var(--foreground);
  background   : transparent;
  border-color : transparent;
  transform    : none;
  box-shadow   : none;
}

.social-btn:active {
  transform  : none;
  box-shadow : none;
}

.social-btn svg {
  width        : 16px;
  height       : 16px;
  stroke-width : 1.6;
}

/* The saweria glyph in links.js is hand-drawn and mixes stroke and fill paths;
   its strokes are pulled to match the 1.6 optical weight of the rest. */
.social-btn[data-id="saweria"] svg [stroke-width="1.5"] {
  stroke-width : 1.7;
}

/* Solid single-path glyphs (whatsapp, telegram) read a full tone darker than
   stroked ones at 16px, so they are held at the dim tone to even the row out. */
.social-btn[data-id="whatsapp"],
.social-btn[data-id="telegram"] {
  color : var(--icon-dim);
}

.social-btn[data-id="whatsapp"],
.social-btn[data-id="github"],
.social-btn[data-id="telegram"],
.social-btn[data-id="saweria"] {
  background   : transparent;
  border-color : transparent;
}

.social-btn[data-id="whatsapp"]:hover,
.social-btn[data-id="github"]:hover,
.social-btn[data-id="telegram"]:hover,
.social-btn[data-id="saweria"]:hover {
  background   : transparent;
  color        : var(--foreground);
  border-color : transparent;
}

/* --------------------------------------------------------------- footer --- */

.footer {
  text-align     : left;
  margin-top     : 2rem;
  padding-top    : 1rem;
  border-top     : 1px solid var(--border);
  font-size      : .6875rem;
  letter-spacing : 0;
  text-transform : none;
  color          : var(--text-muted);
  opacity        : 0;
  animation      : rise .45s .6s var(--ease) both;
}

/* --------------------------------------------------------------- motion --- */

/* `translate`, not `transform`: rcs.js gives each card an animation-delay, and a
   finished animation with fill-mode:both keeps asserting its final transform,
   which would outrank any hover rule. The independent property sidesteps that.
   6px and 450ms — enough to read as arrival, not as an entrance. */
@keyframes rise {
  from {
    opacity   : 0;
    translate : 0 6px;
  }

  to {
    opacity   : 1;
    translate : 0 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration        : .01ms !important;
    animation-iteration-count : 1 !important;
    transition-duration       : .01ms !important;
  }

  .link-card::after {
    transform : none;
  }
}

@media (max-width: 480px) {
  body {
    padding : 3rem 1.25rem;
  }

  .profile h1 {
    font-size : 1.5rem;
  }
}

/* No hover on touch, so the arrow can't rely on it — lift it to the semantic
   icon's tone so it reads as present rather than half-drawn. */
@media (hover: none) {
  .link-card::after {
    color     : var(--icon);
    transform : none;
  }
}
