grain

the design language every arshnah surface is held to. it exists because the sites kept coming out looking generated, and no amount of picking better colours fixed that.

the one idea

generated design is smooth, centered, and safe. hand made design has grain: asymmetry, a signature, one rough edge that is obviously a choice. every screen needs some.

be a system without becoming twins. a shared spine (the anti template rules, Space Grotesk + JetBrains Mono as the fixed type pair, near monochrome color, real texture) and a per project skin (layout, palette, material, the signature move). the spine is what makes them mine. the skin is what keeps them apart.

materials

each one below is drawn in its own tokens, not the page's. showing a material in someone else's colours is not the material.

cipherdropterminaldrop.arshnah.in/cipherdrop

signature: blinking cursor, prompt, film grain

#34d399bg #0a0c0bradius 3pxJetBrains Mono
veildarkroomdrop.arshnah.in/veil

signature: the message vanishing into pixels

#a78bfabg #0b0a0fradius 3pxJetBrains Mono
wisppaper, quietchat.arshnah.in

signature: the peek reveal

#8b9cffbg #0e0e14radius 10pxinherit
larpringxerox flyerlarpring.github.io

signature: grain, stamp, vibe

#9FA1FFbg #0c0c0fradius 2pxinherit
portfoliorefined documentarshnah.in

signature: the theme-following sky

#8fb6ffbg #0a0a12radius 0pxinherit
nowinstrument panelnow.arshnah.in

signature: the clock's colon blinks on a real 1s css cycle

#3ecf6ebg #0a0b0dradius 6pxJetBrains Mono
cardcredential kioskcard.arshnah.in

signature: camera-flash pulse over the card on mint/reroll

#3ddc84bg #0a0c10radius 4pxinherit
shamemuseum placardshame.arshnah.in

signature: glass-case light sweep on exhibit hover

#e0483dbg #0c0a0bradius 2pxinherit
statusswitchboardstatus.arshnah.in

signature: numbered ports boot in a staggered cascade on load

#3ba55dbg #0b0d10radius 3pxJetBrains Mono
usesequipment manifestuses.arshnah.in

signature: a verified stamp fades in on card hover

#8fb6ffbg #0b0d10radius 5pxinherit
buttonsbadge pressbuttons.arshnah.in

signature: the stage punches down like a press on download

#8fb6ffbg #0b0d10radius 9pxinherit
playlistthe crateplaylist.arshnah.in

signature: whatever's actually playing spins like it's on a turntable

#8fb6ffbg #0b0d10radius 8pxinherit
scratchthe split deskscratch.arshnah.in

signature: a perforated dashed seam between write and preview

#8fb6ffbg #0b0d10radius 6pxinherit
slopthe gaugeslop.arshnah.in

signature: the needle overshoots before settling, like a real analog meter

#8fb6ffbg #0b0d10radius 7pxinherit
wrappedterminalwrapped.arshnah.in

signature: a blinking cursor after every huge/big readout

#3ecf6ebg #0a0c0aradius 3pxJetBrains Mono

github profile readme · github.com/arshnah ↗

arshnah's github stats neofetch cardmost used languages by real code volumenow playing, last.fmwhat i'm focused onwhat arsh is doing right now

components

the rail

a list of things. replaces the card, everywhere.

<div style={{ borderLeft: "2px solid var(--accent)", paddingLeft: 14 }}>
  <a href={url}>{name}</a>
  <span className="meta">{tag}</span>
  <p>{desc}</p>
</div>
the leader row

a label and a value, with dots carrying one to the other.

<div className="lrow">
  <span className="k">uptime</span>
  <span className="dots" />          {/* border-bottom: 1px dotted */}
  <span className="v">8 months</span>
</div>
the prompt

a heading, on anything with a terminal material.

<h1 className="mono lower">
  <span className="faint">~/</span>drop<span className="cursor" />
</h1>

/* the cursor is the one flourish. exactly one per project. */
@keyframes blink { 0%,55%{opacity:1} 56%,100%{opacity:0} }
.cursor { width:.55em; height:1.02em; background:var(--accent);
          animation: blink 1.15s step-end infinite; }
film grain

kills the generated look on contact. one fixed overlay, ~0.045.

body::after {
  content:""; position:fixed; inset:0; z-index:9999;
  pointer-events:none; opacity:.045;
  background-image:url("data:image/svg+xml,\
    <svg xmlns='http://www.w3.org/2000/svg' width='140' height='140'>\
      <filter id='g'><feTurbulence type='fractalNoise'\
        baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/></filter>\
      <rect width='100%' height='100%' filter='url(%23g)'/></svg>");
}
the punch

feedback for a physical action, on any kiosk or press material. card's mint/reroll, buttons' download.

function punch(el) {
  el.classList.remove("punch");
  void el.offsetWidth;   // replay on repeat clicks
  el.classList.add("punch");
}

@keyframes punch {
  0%   { transform: scale(1); }
  40%  { transform: scale(.96); }
  100% { transform: scale(1); }
}
.punch { animation: punch .32s ease; }
the boot cascade

a list powering on in sequence before its real state lands. status's numbered ports.

items.forEach((el, i) => {
  el.classList.add("boot");
  el.style.animationDelay = i * 70 + "ms";
  // real data can still land mid-cascade
});

@keyframes boot-flash {
  0%   { opacity: 1; }
  100% { opacity: .4; }
}
.boot { animation: boot-flash .5s ease-out both; }
the overshoot

any value that moves (a needle, a fill, a bar) settles past its mark first, like a real analog gauge. slop's meter.

.needle {
  transition: left .32s cubic-bezier(.34, 1.56, .64, 1);
}
/* same curve works on width, transform, top —
   anything with a target */

the tells

the left column is built wrong on purpose. reading the rule never worked as well as seeing the pair.

never

cipherdrop

encrypted drops

instead

cipherdrop encrypted drops →

share a file the server cannot read.

a rounded card with a centred icon in a tinted square came with the template. nobody chose it. the rail carries the same information and somebody had to decide on it.

never

languages: TypeScript, HTML, CSS…
repos: 17
stars: 6

instead

repos17
stars6

a value parked at a fixed column leaves the rest of the row as nothing. the dots give the whole width a job. and a list where a proportion would do says less than a bar.

never

+ more tools soon

instead

more when there is something worth putting here.

an empty promise holding a grid cell open is still empty. every space earns its keep, and a gap closes with fewer, bigger things. more small ones just make it look busy.

never

“Leverage our comprehensive platform to seamlessly unlock your creative potential — in today's fast-paced world, it's not just a tool, it's a game-changer.”

instead

“built this because the guestbook kept getting sprayed with SQL injections. none of them worked. here's the receipts.”

buzzwords, hollow intensifiers, and the “it's not X, it's Y” contrast structure are the copy equivalent of the icon in a tinted square. nobody chose them, the template did. say the specific true thing instead. (this is exactly what slop.arshnah.in scores.)

the gut check

  1. could a generator produce this in one shot? if yes, it is not done.
  2. what is the one thing a person obviously tuned here?
  3. is the working thing the hero, or is it buried under a pitch?
  4. does it look like my other work in spirit, but not in skin?
  5. would slop.arshnah.in flag the copy? read it out loud. if it sounds like it's pitching instead of telling you a specific true thing, rewrite it.
  6. is there a gap next to a tall element? that's data already being fetched and thrown away, not a spot for more padding or a bigger heading.

the full rules live in project-notes/grain.md. it is a living file. when something turns out to look generated, the fix goes in there before it goes anywhere else.