{"$schema":"https://ui.artbloom.tech/schema/registry-item.json","name":"street-code","type":"registry:page","title":"Street Code","description":"A loud, brutalist one-page developer portfolio: hard black edges, hand-drawn scribbles, a scrolling slogan marquee and a background-free portrait against the dark. Every word lives in one content file; swap the palette and the copy and it is someone else's site.","author":"@artbloom","dependencies":[],"registryDependencies":[],"files":[{"path":"app/street-code/layout.tsx","target":"app/street-code/layout.tsx","content":"import type { Metadata } from \"next\"\n\nimport { SiteFooter } from \"./components/site-footer\"\nimport { SiteHeader } from \"./components/site-header\"\nimport { brand, hero } from \"./data/content\"\n\nimport \"./street-code.css\"\n\nexport const metadata: Metadata = {\n  title: `${brand.name} (${brand.tag}) — ${brand.role}`,\n  description: hero.lede,\n}\n\n/**\n * The template shell.\n *\n * A nested layout, so it renders no `<html>` or `<body>` — the app's root layout\n * owns those. `.scSite` is the single wrapper every rule in `street-code.css`\n * hangs off, which is what keeps this sheet from touching anything else in the\n * app. The grain overlay is a `::before` on this wrapper, so it too is contained.\n *\n * The `<noscript>` block matters: scroll reveals and the drawn-in doodles start\n * hidden in CSS so they cannot flash in before hydration, which means without\n * JavaScript they would never appear. This turns them on for that reader and\n * stops the marquee, rather than showing an empty, half-drawn page.\n */\nexport default function StreetCodeLayout({ children }: { children: React.ReactNode }) {\n  return (\n    <div className=\"scSite\">\n      <noscript>\n        <style\n          dangerouslySetInnerHTML={{\n            __html:\n              \".scReveal{opacity:1;transform:none}.scMarqueeTrack{animation:none}.scDraw path{stroke-dashoffset:0}\",\n          }}\n        />\n      </noscript>\n      <SiteHeader />\n      <main>{children}</main>\n      <SiteFooter />\n    </div>\n  )\n}\n","type":"registry:page"},{"path":"app/street-code/street-code.css","target":"app/street-code/street-code.css","content":"/*\n * street-code — a dark, loud, spray-paint developer portfolio.\n *\n * Nothing here can leak. Every class carries an `sc` prefix and every bare-element\n * rule is scoped under `.scSite`, so the whole sheet is inert outside its own\n * wrapper. A template that ships `.hero` or `.card` into a consumer's globals is a\n * collision waiting to happen; a prefix plus one wrapper makes that impossible.\n *\n * The theme is carried by three things, in order of importance:\n *   1. The hand-drawn marker doodles (see components/doodles.tsx) — the crown, the\n *      loop around a word, the underlines that draw themselves in.\n *   2. One electric green accent, used like spray paint and almost nowhere else.\n *   3. Heavy, oversized, tight-tracked display type in caps.\n * Everything below serves those three. The palette and the display scale are at\n * the top; nothing further down hard-codes a value that is a variable up here.\n */\n.scSite {\n  /* Palette — near-black, off-white, and one acid green. Pink is the rare second marker. */\n  --bg: #0a0a0a;\n  --bg-lift: #141414;\n  --bg-card: #161616;\n  --ink: #f2f2ef;\n  --ink-dim: rgba(242, 242, 239, 0.66);\n  --ink-faint: rgba(242, 242, 239, 0.4);\n  --line: rgba(242, 242, 239, 0.14);\n  --line-strong: rgba(242, 242, 239, 0.9);\n  --accent: #39ff14;\n  --accent-soft: #4ade80;\n  --marker: #ff2d78;\n\n  /* Display scale. Viewport units make the headline monumental on desktop and\n     still legible on a phone; the clamp stops it doing either badly. */\n  --display: clamp(3.4rem, 15vw, 12rem);\n  --heading: clamp(1.9rem, 4.6vw, 3.6rem);\n  --body: clamp(1rem, 1.1vw, 1.15rem);\n\n  --gutter: clamp(1.25rem, 4.5vw, 5.5rem);\n  --section: clamp(4.5rem, 11vh, 9rem);\n  --measure: 40rem;\n\n  /* Heavy grotesque for display; Impact/Arial Black give the sprayed-stencil weight.\n     Body and labels ride the app's loaded fonts so the template needs none of its own. */\n  --font-display: \"Helvetica Neue\", \"Arial Narrow\", \"Arial Black\", Impact, sans-serif;\n  --font-body: var(--font-inter, ui-sans-serif, system-ui, \"Segoe UI\", Arial, sans-serif);\n  --font-mono: var(--font-jetbrains, ui-monospace, \"SFMono-Regular\", \"Cascadia Mono\", monospace);\n\n  position: relative;\n  min-height: 100vh;\n  background: var(--bg);\n  color: var(--ink);\n  font-family: var(--font-body);\n  font-size: var(--body);\n  line-height: 1.6;\n  isolation: isolate;\n  overflow-x: hidden;\n}\n\n.scSite *,\n.scSite *::before,\n.scSite *::after { box-sizing: border-box; }\n\n/*\n * The grain. A tiled SVG turbulence sits over the whole page at low opacity in\n * overlay blend — it is what stops the flat black reading as a plain dark theme\n * and gives it the texture of a photographed wall. Fixed, so it does not scroll,\n * and `pointer-events: none` so it never eats a click.\n */\n.scSite::before {\n  content: \"\";\n  position: fixed;\n  inset: 0;\n  z-index: 1;\n  pointer-events: none;\n  opacity: 0.5;\n  mix-blend-mode: overlay;\n  background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E\");\n}\n\n/* Everything the page renders sits above the grain. */\n.scSite > * { position: relative; z-index: 2; }\n\n/*\n * `:where()` contributes no specificity, so this reset never beats a later class\n * rule — a green button will not inherit a link colour, for instance.\n */\n.scSite :where(a) { color: inherit; text-decoration: none; }\n.scSite :where(button, input, textarea, select) { font: inherit; color: inherit; }\n.scSite :where(ul, ol) { margin: 0; padding: 0; list-style: none; }\n.scSite ::selection { background: var(--accent); color: #000; }\n\n.scSite :focus-visible {\n  outline: 3px solid var(--accent);\n  outline-offset: 3px;\n}\n\n/* --------------------------------------------------------------- primitives */\n\n.scWrap {\n  width: 100%;\n  max-width: 84rem;\n  margin: 0 auto;\n  padding-inline: var(--gutter);\n}\n\n.scSection { padding-block: var(--section); }\n\n/* Wide-tracked micro-label, marked with a green tick. Used for every eyebrow. */\n.scLabel {\n  display: inline-flex;\n  align-items: center;\n  gap: 0.6rem;\n  margin: 0;\n  font-family: var(--font-mono);\n  font-size: 0.72rem;\n  font-weight: 600;\n  letter-spacing: 0.24em;\n  text-transform: uppercase;\n  color: var(--accent);\n}\n\n.scLabel::before {\n  content: \"\";\n  width: 1.6rem;\n  height: 2px;\n  background: var(--accent);\n}\n\n.scLede {\n  max-width: var(--measure);\n  margin: 0;\n  font-size: clamp(1.1rem, 1.7vw, 1.5rem);\n  line-height: 1.5;\n  color: var(--ink-dim);\n  text-wrap: pretty;\n}\n\n/* Big section headings. Heavy, tight, and set in caps like a stencil. */\n.scHeading {\n  position: relative;\n  margin: 0;\n  max-width: 20ch;\n  font-family: var(--font-display);\n  font-size: var(--heading);\n  font-weight: 900;\n  line-height: 0.98;\n  letter-spacing: -0.02em;\n  text-transform: uppercase;\n  text-wrap: balance;\n}\n\n.scSectionHead { margin-bottom: clamp(2.5rem, 6vw, 4rem); }\n.scSectionHead .scLabel { margin-bottom: 1.25rem; }\n\n/* ------------------------------------------------------------------ buttons */\n\n/*\n * Street-poster buttons: a heavy border and a hard offset shadow, no blur. On\n * hover the whole button lunges into its own shadow, so it reads as pressed —\n * kinetic, not decorative.\n */\n.scBtn {\n  --shadow: var(--accent);\n  display: inline-flex;\n  align-items: center;\n  gap: 0.6rem;\n  padding: 0.9rem 1.6rem;\n  border: 2px solid var(--ink);\n  background: transparent;\n  color: var(--ink);\n  font-family: var(--font-mono);\n  font-size: 0.82rem;\n  font-weight: 700;\n  letter-spacing: 0.08em;\n  text-transform: uppercase;\n  cursor: pointer;\n  box-shadow: 5px 5px 0 var(--shadow);\n  transition: transform 0.12s ease, box-shadow 0.12s ease, background-color 0.2s ease,\n    color 0.2s ease;\n}\n\n.scBtn:hover {\n  transform: translate(3px, 3px);\n  box-shadow: 2px 2px 0 var(--shadow);\n}\n\n.scBtn:active {\n  transform: translate(5px, 5px);\n  box-shadow: 0 0 0 var(--shadow);\n}\n\n.scBtnSolid {\n  background: var(--accent);\n  border-color: var(--accent);\n  color: #000;\n  --shadow: var(--ink);\n}\n\n.scBtnSolid:hover { background: var(--accent-soft); border-color: var(--accent-soft); }\n\n.scBtnBig {\n  padding: 1.15rem 2.2rem;\n  font-size: 0.95rem;\n}\n\n.scBtnArrow {\n  display: inline-block;\n  transition: transform 0.2s ease;\n}\n\n.scBtn:hover .scBtnArrow { transform: translateX(4px); }\n\n/* ------------------------------------------------------------------- header */\n\n.scHeader {\n  position: sticky;\n  top: 0;\n  z-index: 40;\n  background: color-mix(in oklab, var(--bg) 84%, transparent);\n  backdrop-filter: blur(12px) saturate(130%);\n  border-bottom: 2px solid var(--ink);\n}\n\n.scHeaderRow {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 1.5rem;\n  height: 4.25rem;\n}\n\n.scBrand {\n  display: inline-flex;\n  align-items: baseline;\n  gap: 0.7rem;\n}\n\n/* The tag, sprayed green with a marker underline scored beneath it. */\n.scBrandTag {\n  position: relative;\n  font-family: var(--font-display);\n  font-size: 1.5rem;\n  font-weight: 900;\n  letter-spacing: -0.02em;\n  color: var(--accent);\n  text-transform: uppercase;\n}\n\n.scBrandTag::after {\n  content: \"\";\n  position: absolute;\n  left: -2px;\n  right: -2px;\n  bottom: -2px;\n  height: 3px;\n  background: var(--accent);\n  transform: skewX(-12deg);\n}\n\n.scBrandHandle {\n  font-family: var(--font-mono);\n  font-size: 0.72rem;\n  letter-spacing: 0.08em;\n  color: var(--ink-faint);\n}\n\n.scNav {\n  display: none;\n  align-items: center;\n  gap: 1.9rem;\n}\n\n.scNavLink {\n  position: relative;\n  font-family: var(--font-mono);\n  font-size: 0.82rem;\n  font-weight: 600;\n  letter-spacing: 0.06em;\n  text-transform: uppercase;\n  color: var(--ink-dim);\n  transition: color 0.2s ease;\n}\n\n.scNavLink:hover { color: var(--ink); }\n\n/* The marker underline grows from the left on hover. */\n.scNavLink::after {\n  content: \"\";\n  position: absolute;\n  left: 0;\n  bottom: -0.4rem;\n  width: 100%;\n  height: 2px;\n  background: var(--accent);\n  transform: scaleX(0);\n  transform-origin: left;\n  transition: transform 0.28s cubic-bezier(0.22, 1, 0.36, 1);\n}\n\n.scNavLink:hover::after { transform: scaleX(1); }\n\n.scNavCta {\n  padding: 0.5rem 1rem;\n  border: 2px solid var(--accent);\n  font-family: var(--font-mono);\n  font-size: 0.8rem;\n  font-weight: 700;\n  letter-spacing: 0.06em;\n  text-transform: uppercase;\n  color: var(--accent);\n  transition: background-color 0.2s ease, color 0.2s ease;\n}\n\n.scNavCta:hover { background: var(--accent); color: #000; }\n\n/* Mobile menu: a native <details>. No state, works without hydration. */\n.scMenu { position: relative; }\n\n.scMenuSummary {\n  display: inline-flex;\n  flex-direction: column;\n  justify-content: center;\n  gap: 5px;\n  width: 2.75rem;\n  height: 2.5rem;\n  padding: 0 0.6rem;\n  border: 2px solid var(--ink);\n  cursor: pointer;\n  list-style: none;\n}\n\n.scMenuSummary::-webkit-details-marker { display: none; }\n.scMenuBar { display: block; height: 2px; background: var(--ink); }\n.scMenu[open] .scMenuSummary { border-color: var(--accent); }\n.scMenu[open] .scMenuBar { background: var(--accent); }\n\n.scMenuPanel {\n  position: absolute;\n  right: 0;\n  top: calc(100% + 0.7rem);\n  z-index: 50;\n  display: grid;\n  gap: 0.25rem;\n  min-width: 12rem;\n  padding: 0.6rem;\n  border: 2px solid var(--ink);\n  background: var(--bg-lift);\n  box-shadow: 6px 6px 0 var(--accent);\n}\n\n.scMenuLink {\n  padding: 0.65rem 0.75rem;\n  font-family: var(--font-mono);\n  font-size: 0.85rem;\n  font-weight: 600;\n  letter-spacing: 0.06em;\n  text-transform: uppercase;\n  color: var(--ink-dim);\n}\n\n.scMenuLink:hover { background: rgba(242, 242, 239, 0.06); color: var(--ink); }\n.scMenuLinkCta { color: var(--accent); }\n\n@media (min-width: 56rem) {\n  .scNav { display: flex; }\n  .scMenu { display: none; }\n  .scHeaderRow { height: 5rem; }\n}\n\n/* --------------------------------------------------------------------- hero */\n\n.scHero {\n  display: grid;\n  gap: clamp(2.5rem, 6vw, 4rem);\n  padding-top: clamp(2.5rem, 7vh, 5rem);\n  padding-bottom: clamp(2.5rem, 6vh, 4rem);\n  align-items: center;\n}\n\n/* A soft green haze behind the hero, so the portrait does not float on flat black. */\n.scHero::before {\n  content: \"\";\n  position: absolute;\n  top: -10%;\n  right: -5%;\n  width: 42rem;\n  height: 42rem;\n  max-width: 90vw;\n  z-index: -1;\n  border-radius: 50%;\n  background: radial-gradient(circle, rgba(57, 255, 20, 0.16), transparent 62%);\n  filter: blur(30px);\n  pointer-events: none;\n}\n\n.scHeroCopy {\n  display: grid;\n  gap: clamp(1.5rem, 3vw, 2.25rem);\n  align-content: start;\n}\n\n.scKicker {\n  margin: 0;\n  font-family: var(--font-mono);\n  font-size: 0.8rem;\n  font-weight: 600;\n  letter-spacing: 0.14em;\n  text-transform: uppercase;\n  color: var(--ink-faint);\n}\n\n/*\n * The headline. One hard word per line, transformed taller than it is wide for\n * the condensed, spray-stencil look — `scaleY` rather than a variable font so no\n * webfont is needed. `overflow: visible` lets the crown and loop spill out.\n */\n.scDisplay {\n  position: relative;\n  margin: 0;\n  font-family: var(--font-display);\n  font-size: var(--display);\n  font-weight: 900;\n  line-height: 0.82;\n  letter-spacing: -0.03em;\n  text-transform: uppercase;\n}\n\n.scDisplayWord {\n  display: block;\n  transform: scaleY(1.14);\n  transform-origin: left top;\n}\n\n/* The circled word is the one the eye lands on — green, with a marker loop. */\n.scDisplayWordMark {\n  position: relative;\n  display: inline-block;\n  color: var(--accent);\n}\n\n/* The crown, sprayed above the first line and tilted like a hand drew it. */\n.scCrown {\n  position: absolute;\n  top: clamp(-2.6rem, -6vw, -4.2rem);\n  left: -0.4rem;\n  width: clamp(3.6rem, 9vw, 6.5rem);\n  height: auto;\n  color: var(--accent);\n  transform: rotate(-8deg);\n  overflow: visible;\n}\n\n/* The loop around the circled word, sized past the glyphs so it reads as a ring. */\n.scWordLoop {\n  position: absolute;\n  top: 50%;\n  left: 50%;\n  width: 128%;\n  height: 176%;\n  transform: translate(-50%, -50%) rotate(-3deg);\n  color: var(--marker);\n  overflow: visible;\n  pointer-events: none;\n}\n\n.scHeroActions {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 0.9rem;\n  align-items: center;\n}\n\n/* Stats row — mono labels under heavy green numbers, split by hairlines. */\n.scHeroStats {\n  display: flex;\n  flex-wrap: wrap;\n  gap: clamp(1.5rem, 4vw, 3rem);\n  margin: 0.5rem 0 0;\n  padding-top: 1.75rem;\n  border-top: 2px solid var(--line);\n}\n\n.scStat { display: grid; gap: 0.15rem; }\n\n.scStatValue {\n  font-family: var(--font-display);\n  font-size: clamp(1.9rem, 4vw, 2.9rem);\n  font-weight: 900;\n  line-height: 1;\n  letter-spacing: -0.02em;\n  color: var(--accent);\n}\n\n.scStatLabel {\n  margin: 0;\n  font-family: var(--font-mono);\n  font-size: 0.72rem;\n  letter-spacing: 0.1em;\n  text-transform: uppercase;\n  color: var(--ink-faint);\n}\n\n/* ------------------------------------------------------------ hero portrait */\n\n.scPortrait {\n  position: relative;\n  justify-self: center;\n  width: min(100%, 30rem);\n}\n\n/*\n * The frame is tilted a couple of degrees, bordered heavy, and throws a hard\n * green shadow — a poster stapled to a wall, not a headshot. The portrait's own\n * black background blends into the card, so only the figure shows.\n */\n.scPortraitFrame {\n  position: relative;\n  border: 3px solid var(--ink);\n  background: #000;\n  box-shadow: 12px 12px 0 var(--accent);\n  transform: rotate(-2deg);\n  overflow: visible;\n}\n\n.scPortraitImg {\n  display: block;\n  width: 100%;\n  height: auto;\n  filter: grayscale(1) contrast(1.15);\n  mix-blend-mode: lighten;\n}\n\n/* A scribbled corner bracket, top-left, overhanging the frame. */\n.scPortraitCorner {\n  position: absolute;\n  top: -1.5rem;\n  left: -1.5rem;\n  width: 5rem;\n  height: 5rem;\n  color: var(--accent);\n  transform: rotate(-6deg);\n}\n\n/* A spark near the shoulder. */\n.scPortraitSpark {\n  position: absolute;\n  right: -1.2rem;\n  top: 22%;\n  width: 2.5rem;\n  height: 2.5rem;\n  color: var(--marker);\n}\n\n/* The \"yeah, that's me\" note, hand-scrawled and arrowed at the portrait. */\n.scPortraitNote {\n  position: absolute;\n  right: -0.5rem;\n  bottom: -3.5rem;\n  display: flex;\n  align-items: flex-start;\n  gap: 0.25rem;\n  transform: rotate(-4deg);\n}\n\n.scPortraitNoteText {\n  font-family: var(--font-display);\n  font-size: 1.5rem;\n  font-weight: 700;\n  letter-spacing: 0.01em;\n  color: var(--ink);\n}\n\n.scPortraitArrow {\n  width: 3.25rem;\n  height: 3rem;\n  color: var(--accent);\n  transform: rotate(6deg) scaleX(-1);\n}\n\n@media (min-width: 60rem) {\n  .scHero { grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr); }\n  .scPortrait { justify-self: end; }\n}\n\n/* ------------------------------------------------------------------ marquee */\n\n/*\n * The spray-paint ticker. Alternating filled and outlined words give it the\n * layered-poster look; a green diamond sits between each. The track is doubled in\n * the component and shifted -50%, so the loop is seamless.\n */\n.scMarquee {\n  border-block: 3px solid var(--ink);\n  background: var(--bg-lift);\n  padding-block: 1.1rem;\n  overflow: hidden;\n}\n\n.scMarqueeTrack {\n  display: flex;\n  width: max-content;\n  animation: scMarquee 26s linear infinite;\n}\n\n.scMarqueeItem {\n  display: inline-flex;\n  align-items: center;\n  gap: 2.5rem;\n  padding-right: 2.5rem;\n  font-family: var(--font-display);\n  font-size: clamp(1.4rem, 3.4vw, 2.4rem);\n  font-weight: 900;\n  letter-spacing: 0.01em;\n  text-transform: uppercase;\n  white-space: nowrap;\n  color: var(--ink);\n}\n\n/* Every other phrase is outlined instead of filled — depth without a second colour. */\n.scMarqueeItem:nth-child(even) {\n  color: transparent;\n  -webkit-text-stroke: 1.5px var(--ink);\n}\n\n/* The diamond separator. */\n.scMarqueeItem::after {\n  content: \"\";\n  width: 0.7rem;\n  height: 0.7rem;\n  background: var(--accent);\n  transform: rotate(45deg);\n  flex-shrink: 0;\n}\n\n@keyframes scMarquee {\n  from { transform: translate3d(0, 0, 0); }\n  to { transform: translate3d(-50%, 0, 0); }\n}\n\n/* -------------------------------------------------------------------- about */\n\n.scAbout { display: grid; gap: clamp(2rem, 5vw, 3.5rem); }\n\n.scAboutHead { display: grid; gap: 1.25rem; align-content: start; }\n\n/* The marker underline under the about heading draws in when it scrolls up. */\n.scHeadingRule {\n  display: block;\n  width: clamp(9rem, 22vw, 15rem);\n  height: auto;\n  margin-top: 0.6rem;\n  color: var(--accent);\n  overflow: visible;\n}\n\n.scAboutBody { display: grid; gap: 1.4rem; }\n\n.scAboutBody p {\n  margin: 0;\n  max-width: var(--measure);\n  font-size: clamp(1.05rem, 1.4vw, 1.28rem);\n  line-height: 1.6;\n  color: var(--ink-dim);\n  text-wrap: pretty;\n}\n\n/* The opener carries the voice; make it read louder than the rest. */\n.scAboutBody p:first-child {\n  color: var(--ink);\n  font-size: clamp(1.2rem, 1.9vw, 1.6rem);\n  line-height: 1.45;\n}\n\n.scAboutSign {\n  font-family: var(--font-display);\n  font-weight: 900;\n  font-size: 1.5rem;\n  letter-spacing: 0.02em;\n  color: var(--accent);\n  text-transform: uppercase;\n}\n\n@media (min-width: 60rem) {\n  .scAbout { grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr); align-items: start; }\n}\n\n/* ----------------------------------------------------------------- projects */\n\n.scCards {\n  display: grid;\n  gap: clamp(1.5rem, 3vw, 2.25rem);\n}\n\n/*\n * Each project is a street poster: heavy ink border, hard green offset shadow,\n * lifted background. On hover it jumps up-left into a bigger pink shadow — the\n * one place the second marker colour earns its keep.\n */\n.scCard {\n  display: grid;\n  gap: 1.1rem;\n  align-content: start;\n  padding: clamp(1.5rem, 3vw, 2.25rem);\n  border: 3px solid var(--ink);\n  background: var(--bg-card);\n  box-shadow: 8px 8px 0 var(--accent);\n  transition: transform 0.16s ease, box-shadow 0.16s ease;\n}\n\n.scCard:hover,\n.scCard:focus-within {\n  transform: translate(-4px, -4px);\n  box-shadow: 14px 14px 0 var(--marker);\n}\n\n.scCardTop {\n  display: flex;\n  align-items: baseline;\n  justify-content: space-between;\n  gap: 1rem;\n}\n\n.scCardIndex {\n  font-family: var(--font-display);\n  font-size: clamp(2.4rem, 5vw, 3.6rem);\n  font-weight: 900;\n  line-height: 0.8;\n  letter-spacing: -0.04em;\n  color: transparent;\n  -webkit-text-stroke: 2px var(--accent);\n}\n\n.scCardYear {\n  font-family: var(--font-mono);\n  font-size: 0.78rem;\n  letter-spacing: 0.1em;\n  color: var(--ink-faint);\n}\n\n.scCardTitle {\n  margin: 0;\n  font-family: var(--font-display);\n  font-size: clamp(1.8rem, 3.4vw, 2.6rem);\n  font-weight: 900;\n  line-height: 0.95;\n  letter-spacing: -0.01em;\n  text-transform: uppercase;\n}\n\n.scCardBlurb {\n  margin: 0;\n  color: var(--ink-dim);\n  line-height: 1.55;\n  text-wrap: pretty;\n}\n\n.scTags {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 0.5rem;\n}\n\n.scTag {\n  padding: 0.3rem 0.7rem;\n  border: 1.5px solid var(--line-strong);\n  font-family: var(--font-mono);\n  font-size: 0.72rem;\n  font-weight: 600;\n  letter-spacing: 0.06em;\n  text-transform: uppercase;\n  color: var(--ink);\n}\n\n.scCardLink {\n  display: inline-flex;\n  align-items: center;\n  gap: 0.5rem;\n  justify-self: start;\n  font-family: var(--font-mono);\n  font-size: 0.82rem;\n  font-weight: 700;\n  letter-spacing: 0.08em;\n  text-transform: uppercase;\n  color: var(--accent);\n  border-bottom: 2px solid transparent;\n  transition: border-color 0.2s ease;\n}\n\n.scCardLink:hover { border-color: var(--accent); }\n.scCardLink:hover .scBtnArrow { transform: translate(2px, -2px); }\n\n@media (min-width: 48rem) {\n  .scCards { grid-template-columns: repeat(2, minmax(0, 1fr)); }\n}\n\n/* ------------------------------------------------------------------- skills */\n\n.scSkills {\n  display: grid;\n  gap: clamp(1.5rem, 3vw, 2.5rem);\n}\n\n.scSkillGroup {\n  display: grid;\n  gap: 1.1rem;\n  align-content: start;\n  padding-top: 1.5rem;\n  border-top: 3px solid var(--ink);\n}\n\n.scSkillTitle {\n  display: flex;\n  align-items: center;\n  gap: 0.6rem;\n  margin: 0;\n  font-family: var(--font-display);\n  font-size: clamp(1.3rem, 2.4vw, 1.75rem);\n  font-weight: 900;\n  letter-spacing: -0.01em;\n  text-transform: uppercase;\n}\n\n.scSkillSpark {\n  width: 1.15rem;\n  height: 1.15rem;\n  color: var(--accent);\n  flex-shrink: 0;\n}\n\n.scSkillList { display: grid; gap: 0.7rem; }\n\n.scSkillItem {\n  display: flex;\n  align-items: center;\n  gap: 0.7rem;\n  font-size: 1.02rem;\n  color: var(--ink-dim);\n}\n\n/* A green spray-tick before each skill. */\n.scSkillItem::before {\n  content: \"\";\n  width: 0.85rem;\n  height: 2px;\n  background: var(--accent);\n  flex-shrink: 0;\n}\n\n@media (min-width: 52rem) {\n  .scSkills { grid-template-columns: repeat(3, minmax(0, 1fr)); }\n}\n\n/* ------------------------------------------------------------------ contact */\n\n.scContactSection { padding-bottom: clamp(3rem, 8vw, 6rem); }\n\n.scContact {\n  display: grid;\n  gap: 1.75rem;\n  justify-items: start;\n}\n\n.scContactHeading {\n  position: relative;\n  margin: 0;\n  max-width: 18ch;\n  font-family: var(--font-display);\n  font-size: clamp(2.4rem, 7vw, 5.5rem);\n  font-weight: 900;\n  line-height: 0.94;\n  letter-spacing: -0.03em;\n  text-transform: uppercase;\n  text-wrap: balance;\n}\n\n.scContactRule { width: clamp(12rem, 32vw, 22rem); }\n\n.scSocials {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 0.9rem;\n  margin-top: 0.75rem;\n}\n\n.scSocial {\n  display: grid;\n  gap: 0.2rem;\n  padding: 0.9rem 1.2rem;\n  border: 2px solid var(--line);\n  transition: border-color 0.2s ease, transform 0.16s ease;\n}\n\n.scSocial:hover { border-color: var(--accent); transform: translateY(-3px); }\n\n.scSocialLabel {\n  font-family: var(--font-mono);\n  font-size: 0.7rem;\n  letter-spacing: 0.12em;\n  text-transform: uppercase;\n  color: var(--ink-faint);\n}\n\n.scSocialHandle { font-weight: 600; color: var(--ink); }\n\n/* ------------------------------------------------------------------- footer */\n\n.scFooter {\n  position: relative;\n  border-top: 3px solid var(--ink);\n  padding-top: clamp(3rem, 7vw, 4.5rem);\n  overflow: hidden;\n}\n\n.scFooterTop {\n  display: grid;\n  gap: 2.5rem;\n  padding-bottom: 2.5rem;\n}\n\n.scFooterBlurb {\n  margin: 0;\n  max-width: 34rem;\n  font-size: 0.95rem;\n  line-height: 1.6;\n  color: var(--ink-dim);\n  text-wrap: pretty;\n}\n\n.scFooterNav {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 1rem 1.75rem;\n  align-content: start;\n}\n\n.scFooterLink {\n  font-family: var(--font-mono);\n  font-size: 0.82rem;\n  font-weight: 600;\n  letter-spacing: 0.06em;\n  text-transform: uppercase;\n  color: var(--ink-dim);\n  transition: color 0.2s ease;\n}\n\n.scFooterLink:hover { color: var(--accent); }\n\n.scFooterBottom {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 0.75rem 2rem;\n  justify-content: space-between;\n  padding-block: 1.6rem;\n  border-top: 2px solid var(--line);\n  font-family: var(--font-mono);\n  font-size: 0.72rem;\n  letter-spacing: 0.08em;\n  text-transform: uppercase;\n  color: var(--ink-faint);\n}\n\n.scFooterBottom a { transition: color 0.2s ease; }\n.scFooterBottom a:hover { color: var(--accent); }\n\n/*\n * The closing tag, sized to run off both edges and get clipped by the footer —\n * a name sprayed bigger than its wall. Outlined, not filled, so it stays a\n * backdrop and never fights the copy above it.\n */\n.scFooterTag {\n  margin: 0;\n  font-family: var(--font-display);\n  font-size: clamp(6rem, 34vw, 30rem);\n  font-weight: 900;\n  line-height: 0.72;\n  letter-spacing: -0.04em;\n  text-align: center;\n  color: transparent;\n  -webkit-text-stroke: 2px rgba(57, 255, 20, 0.28);\n  white-space: nowrap;\n  user-select: none;\n}\n\n@media (min-width: 52rem) {\n  .scFooterTop { grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr); gap: 4rem; }\n}\n\n/* ------------------------------------------------------------- scroll reveal */\n\n/*\n * Reveal on scroll, driven by the one IntersectionObserver in components/reveal.tsx\n * that flips `data-shown`. The hidden state lives here, not in JS, so nothing\n * flashes in before the observer attaches.\n */\n.scReveal {\n  opacity: 0;\n  transform: translate3d(0, 1.5rem, 0);\n  transition:\n    opacity 0.7s cubic-bezier(0.22, 1, 0.36, 1),\n    transform 0.7s cubic-bezier(0.22, 1, 0.36, 1);\n  transition-delay: var(--sc-delay, 0ms);\n}\n\n.scReveal[data-shown=\"true\"] {\n  opacity: 1;\n  transform: translate3d(0, 0, 0);\n}\n\n/* ---------------------------------------------------------- drawn-in doodles */\n\n/*\n * The marker draw-in. Every stroked path in a `.scDraw` mark uses `pathLength={1}`,\n * so setting the dash array and offset to 1 hides the whole line, and animating\n * the offset to 0 \"draws\" it. Two triggers:\n *   - `.scDrawNow` fires on load, after a beat — for the hero marks, which are on\n *     screen before any scroll.\n *   - Inside a reveal, the draw waits for `data-shown` — so a heading's underline\n *     paints itself exactly as the block arrives.\n */\n.scDraw path {\n  stroke-dasharray: 1;\n  stroke-dashoffset: 1;\n}\n\n/* The lighter second underline pass hangs back so the two strokes land in sequence. */\n.scDraw .scUnderlineSecond { transition-delay: 0.15s; }\n\n@keyframes scDrawIn {\n  to { stroke-dashoffset: 0; }\n}\n\n.scDrawNow path {\n  animation: scDrawIn 0.9s cubic-bezier(0.65, 0, 0.35, 1) forwards;\n}\n\n.scCrown.scDrawNow path { animation-delay: 0.15s; }\n.scWordLoop.scDrawNow path { animation-delay: 0.5s; }\n.scPortraitArrow.scDrawNow path { animation-delay: 0.8s; }\n\n/* A reveal-triggered mark draws with a transition instead of the load animation. */\n.scReveal[data-shown=\"true\"] .scDraw path {\n  transition: stroke-dashoffset 0.9s cubic-bezier(0.65, 0, 0.35, 1);\n  transition-delay: 0.2s;\n  stroke-dashoffset: 0;\n}\n\n/* ------------------------------------------------------- reduced motion */\n\n/*\n * Under reduced-motion everything is simply present: reveals visible with no\n * transform, doodles fully drawn with no animation, and the marquee stopped —\n * it scrolls on overflow instead, so its phrases stay reachable.\n */\n@media (prefers-reduced-motion: reduce) {\n  .scReveal,\n  .scReveal[data-shown=\"true\"] {\n    opacity: 1;\n    transform: none;\n    transition: none;\n  }\n\n  .scDraw path,\n  .scDrawNow path,\n  .scReveal[data-shown=\"true\"] .scDraw path {\n    stroke-dashoffset: 0;\n    animation: none;\n    transition: none;\n  }\n\n  .scMarquee { overflow-x: auto; }\n  .scMarqueeTrack { animation: none; }\n\n  .scSite *,\n  .scSite *::before,\n  .scSite *::after {\n    transition-duration: 0.01ms !important;\n    animation-duration: 0.01ms !important;\n    animation-iteration-count: 1 !important;\n    scroll-behavior: auto !important;\n  }\n}\n","type":"registry:style"},{"path":"app/street-code/page.tsx","target":"app/street-code/page.tsx","content":"import { Reveal } from \"./components/reveal\"\nimport { Ticker } from \"./components/ticker\"\nimport {\n  Arrow,\n  CircleLoop,\n  CornerScribble,\n  Crown,\n  Spark,\n  Underline,\n} from \"./components/doodles\"\nimport { about, brand, contact, hero, projects, skills } from \"./data/content\"\n\n/**\n * The street-code portfolio: one long scroll.\n *\n * The order is the pitch — a shout, proof it's earned, the story behind it, the\n * kit, then the ask. Every string comes from `data/content.ts`; the only things\n * hard-coded in JSX are the graffiti marks, and those are the point.\n *\n * `scDraw` + `scDrawNow` on a doodle draws its stroke in on load; a doodle inside\n * a `<Reveal>` draws when that block scrolls into view instead.\n */\nexport default function StreetCodeHome() {\n  return (\n    <>\n      {/* ------------------------------------------------------------ hero */}\n      <section className=\"scWrap scHero\">\n        <div className=\"scHeroCopy\">\n          <p className=\"scKicker\">{hero.kicker}</p>\n\n          <h1 className=\"scDisplay\">\n            {/* The crown sits over the first line, sprayed on. */}\n            <Crown className=\"scCrown scDraw scDrawNow\" />\n            {hero.headline.map((word) => {\n              const circled = word === hero.circled\n              return (\n                <span\n                  key={word}\n                  className={circled ? \"scDisplayWord scDisplayWordMark\" : \"scDisplayWord\"}\n                >\n                  {word}\n                  {circled && <CircleLoop className=\"scWordLoop scDraw scDrawNow\" />}\n                </span>\n              )\n            })}\n          </h1>\n\n          <p className=\"scLede\">{hero.lede}</p>\n\n          <div className=\"scHeroActions\">\n            <a className=\"scBtn scBtnSolid\" href={hero.primary.href}>\n              {hero.primary.label}\n              <span className=\"scBtnArrow\" aria-hidden=\"true\">\n                →\n              </span>\n            </a>\n            <a className=\"scBtn\" href={hero.secondary.href}>\n              {hero.secondary.label}\n            </a>\n          </div>\n\n          <dl className=\"scHeroStats\">\n            {hero.stats.map((stat) => (\n              <div key={stat.label} className=\"scStat\">\n                <dt className=\"scStatValue\">{stat.value}</dt>\n                <dd className=\"scStatLabel\">{stat.label}</dd>\n              </div>\n            ))}\n          </dl>\n        </div>\n\n        {/* The B&W portrait, with the green marker doodles layered over it. */}\n        <div className=\"scPortrait\">\n          <div className=\"scPortraitFrame\">\n            {/* eslint-disable-next-line @next/next/no-img-element -- static asset, no optimization needed */}\n            <img\n              className=\"scPortraitImg\"\n              src={hero.portrait.src}\n              alt={hero.portrait.alt}\n              width={1024}\n              height={1024}\n            />\n            <CornerScribble className=\"scPortraitCorner\" />\n            <Spark className=\"scPortraitSpark\" />\n          </div>\n          <div className=\"scPortraitNote\">\n            <span className=\"scPortraitNoteText\">{hero.portraitNote}</span>\n            <Arrow className=\"scPortraitArrow scDraw scDrawNow\" />\n          </div>\n        </div>\n      </section>\n\n      <Ticker />\n\n      {/* ----------------------------------------------------------- about */}\n      <section id=\"about\" className=\"scSection\">\n        <div className=\"scWrap\">\n          <Reveal className=\"scAbout\">\n            <div className=\"scAboutHead\">\n              <p className=\"scLabel\">{about.label}</p>\n              <h2 className=\"scHeading\">\n                {about.heading}\n                <Underline className=\"scHeadingRule scDraw\" />\n              </h2>\n            </div>\n            <div className=\"scAboutBody\">\n              {about.body.map((paragraph) => (\n                <p key={paragraph}>{paragraph}</p>\n              ))}\n              <p className=\"scAboutSign\">{about.signature}</p>\n            </div>\n          </Reveal>\n        </div>\n      </section>\n\n      {/* ------------------------------------------------------------ work */}\n      <section id=\"work\" className=\"scSection\">\n        <div className=\"scWrap\">\n          <Reveal className=\"scSectionHead\">\n            <p className=\"scLabel\">{projects.label}</p>\n            <h2 className=\"scHeading\">{projects.heading}</h2>\n          </Reveal>\n\n          <div className=\"scCards\">\n            {projects.items.map((project, index) => (\n              <Reveal key={project.index} className=\"scCard\" delay={index * 80}>\n                <div className=\"scCardTop\">\n                  <span className=\"scCardIndex\">{project.index}</span>\n                  <span className=\"scCardYear\">{project.year}</span>\n                </div>\n                <h3 className=\"scCardTitle\">{project.title}</h3>\n                <p className=\"scCardBlurb\">{project.blurb}</p>\n                <ul className=\"scTags\">\n                  {project.tags.map((tag) => (\n                    <li key={tag} className=\"scTag\">\n                      {tag}\n                    </li>\n                  ))}\n                </ul>\n                <a\n                  className=\"scCardLink\"\n                  href={project.link.href}\n                  target=\"_blank\"\n                  rel=\"noreferrer\"\n                >\n                  {project.link.label}\n                  <span className=\"scBtnArrow\" aria-hidden=\"true\">\n                    ↗\n                  </span>\n                </a>\n              </Reveal>\n            ))}\n          </div>\n        </div>\n      </section>\n\n      {/* ---------------------------------------------------------- skills */}\n      <section id=\"skills\" className=\"scSection\">\n        <div className=\"scWrap\">\n          <Reveal className=\"scSectionHead\">\n            <p className=\"scLabel\">{skills.label}</p>\n            <h2 className=\"scHeading\">{skills.heading}</h2>\n          </Reveal>\n\n          <div className=\"scSkills\">\n            {skills.groups.map((group, index) => (\n              <Reveal key={group.title} className=\"scSkillGroup\" delay={index * 80}>\n                <h3 className=\"scSkillTitle\">\n                  <Spark className=\"scSkillSpark\" />\n                  {group.title}\n                </h3>\n                <ul className=\"scSkillList\">\n                  {group.items.map((item) => (\n                    <li key={item} className=\"scSkillItem\">\n                      {item}\n                    </li>\n                  ))}\n                </ul>\n              </Reveal>\n            ))}\n          </div>\n        </div>\n      </section>\n\n      {/* --------------------------------------------------------- contact */}\n      <section id=\"contact\" className=\"scSection scContactSection\">\n        <div className=\"scWrap\">\n          <Reveal className=\"scContact\">\n            <p className=\"scLabel\">{contact.label}</p>\n            <h2 className=\"scContactHeading\">\n              {contact.heading}\n              <Underline className=\"scHeadingRule scContactRule scDraw\" />\n            </h2>\n            <p className=\"scLede\">{contact.lede}</p>\n\n            <a className=\"scBtn scBtnSolid scBtnBig\" href={`mailto:${brand.email}`}>\n              {contact.emailLabel}\n              <span className=\"scBtnArrow\" aria-hidden=\"true\">\n                →\n              </span>\n            </a>\n\n            <ul className=\"scSocials\">\n              {contact.socials.map((social) => (\n                <li key={social.label}>\n                  <a className=\"scSocial\" href={social.href} target=\"_blank\" rel=\"noreferrer\">\n                    <span className=\"scSocialLabel\">{social.label}</span>\n                    <span className=\"scSocialHandle\">{social.handle}</span>\n                  </a>\n                </li>\n              ))}\n            </ul>\n          </Reveal>\n        </div>\n      </section>\n    </>\n  )\n}\n","type":"registry:page"},{"path":"app/street-code/data/content.ts","target":"app/street-code/data/content.ts","content":"/**\n * Everything the street-code portfolio says, in one file.\n *\n * The page and its components hold no copy of their own — they read from here.\n * Re-voicing the whole site for a different developer is one file to edit and no\n * JSX to touch. Swap the palette in `street-code.css`, swap the words here, drop\n * in a new portrait at `/street-code/portrait.png`, and it is someone else's site.\n *\n * The demo persona is a Brooklyn street-artist-turned-developer, because the\n * template's whole reason for existing is attitude and this is a voice that can\n * carry it. Replace it with yours.\n */\n\n/** Where the template is mounted. The brand mark links here; nav uses in-page hashes. */\nexport const BASE = \"/street-code\"\n\nexport const brand = {\n  /** The tag. Sprayed big in the header and footer — keep it short and loud. */\n  tag: \"RIOT\",\n  name: \"Marcus Vega\",\n  handle: \"@riot.codes\",\n  role: \"Full-stack dev & creative technologist\",\n  city: \"Brooklyn, NY\",\n  email: \"hey@riot.codes\",\n  year: 2026,\n} as const\n\n/** In-page anchors — this is one long scroll, so nav jumps rather than routes. */\nexport const nav = [\n  { label: \"Work\", href: \"#work\" },\n  { label: \"About\", href: \"#about\" },\n  { label: \"Skills\", href: \"#skills\" },\n  { label: \"Contact\", href: \"#contact\" },\n] as const\n\n/**\n * The hero. `headline` is set one word per line at display size — three short,\n * hard words read best, and the line breaks are the composition. `circled` names\n * the word that gets the hand-drawn scribble loop around it.\n */\nexport const hero = {\n  kicker: \"Freelance dev for hire — Brooklyn, since '14\",\n  headline: [\"IDEAS\", \"INTO\", \"CODE\"] as const,\n  circled: \"CODE\",\n  lede:\n    \"I build loud, fast, unmistakable web things for brands that refuse to blend in. Ten years turning half-baked 2am ideas into production code that actually ships.\",\n  primary: { label: \"See the work\", href: \"#work\" },\n  secondary: { label: \"Start something\", href: \"#contact\" },\n  portrait: {\n    src: `${BASE}/portrait.png`,\n    alt: \"Marcus Vega — hood up, shades on, against a black wall\",\n  },\n  /** The scribbled note pinned to the portrait. */\n  portraitNote: \"yeah, that's me\",\n  stats: [\n    { value: \"10+\", label: \"years shipping\" },\n    { value: \"60+\", label: \"projects live\" },\n    { value: \"3k\", label: \"github stars\" },\n  ] as const,\n} as const\n\n/**\n * The strip that scrolls under the hero. Short, punchy, no punctuation — half\n * slogans, half skills, all attitude. Rendered twice so the loop never jumps.\n */\nexport const marquee = [\n  \"IDEAS INTO CODE\",\n  \"SHIP IT LOUD\",\n  \"NO ROUNDED CORNERS\",\n  \"TYPESCRIPT ALL DAY\",\n  \"MADE IN BROOKLYN\",\n  \"STAY DANGEROUS\",\n] as const\n\nexport const about = {\n  label: \"Who's asking\",\n  heading: \"I write code like it's on a wall — bold, fast, and impossible to ignore.\",\n  body: [\n    \"Started tagging HTML in a Bushwick basement in 2014 and never really stopped. Somewhere between the spray cans and the standups I worked out that shipping software is the same game as painting a wall: show up, commit to the line, and don't second-guess it halfway through.\",\n    \"These days I run point on front-of-stack builds — design systems, WebGL toys, audio experiments, the occasional Rust CLI when I'm feeling feral. I care about type that hits, motion that actually means something, and code the next person can read without cursing my name.\",\n    \"No agencies, no middlemen, no “it depends.” You bring the idea. I bring the paint.\",\n  ] as const,\n  signature: \"— RIOT\",\n} as const\n\nexport const projects = {\n  label: \"Selected work\",\n  heading: \"Four things I shipped and would ship again\",\n  items: [\n    {\n      index: \"01\",\n      title: \"HALFTONE\",\n      blurb:\n        \"A design-token pipeline that syncs Figma variables to CSS, Swift, and Kotlin on every merge. Runs the design system at two YC-backed startups.\",\n      tags: [\"TypeScript\", \"Figma API\", \"Turborepo\"],\n      year: \"2025\",\n      link: { label: \"Source\", href: \"https://github.com/riotcodes/halftone\" },\n    },\n    {\n      index: \"02\",\n      title: \"RIOT.FM\",\n      blurb:\n        \"A browser drum machine and step sequencer built on raw Web Audio — no samples, no backend. Forty thousand beats made and counting.\",\n      tags: [\"Web Audio\", \"React\", \"Canvas\"],\n      year: \"2024\",\n      link: { label: \"Live demo\", href: \"https://riot.fm\" },\n    },\n    {\n      index: \"03\",\n      title: \"DEADLINE\",\n      blurb:\n        \"A terminal-first release tool that turns messy git history into a changelog you'd actually publish. Three thousand stars, one long weekend.\",\n      tags: [\"Rust\", \"CLI\", \"Git\"],\n      year: \"2023\",\n      link: { label: \"Source\", href: \"https://github.com/riotcodes/deadline\" },\n    },\n    {\n      index: \"04\",\n      title: \"CONCRETE\",\n      blurb:\n        \"A brutalist React component library. Heavy borders, hard offset shadows, zero rounded corners, and WCAG-clean straight out of the box.\",\n      tags: [\"React\", \"CSS\", \"a11y\"],\n      year: \"2025\",\n      link: { label: \"Docs\", href: \"https://concrete.riot.codes\" },\n    },\n  ] as const,\n} as const\n\nexport const skills = {\n  label: \"The kit\",\n  heading: \"What's in the bag\",\n  groups: [\n    {\n      title: \"Front of stack\",\n      items: [\"React\", \"Next.js\", \"TypeScript\", \"WebGL / Three\", \"Motion\"],\n    },\n    {\n      title: \"Back of stack\",\n      items: [\"Node\", \"Postgres\", \"Rust\", \"Redis\", \"Edge functions\"],\n    },\n    {\n      title: \"The craft\",\n      items: [\"Design systems\", \"Creative coding\", \"Accessibility\", \"Performance\", \"Type\"],\n    },\n  ] as const,\n} as const\n\nexport const contact = {\n  label: \"Say something\",\n  heading: \"Got something that shouldn't look like everything else?\",\n  lede:\n    \"Freelance slots open for Q3. Tell me what you're building — I reply within a day, usually a lot less.\",\n  emailLabel: \"Hit the line\",\n  socials: [\n    { label: \"GitHub\", handle: \"github.com/riotcodes\", href: \"https://github.com/riotcodes\" },\n    { label: \"Twitter / X\", handle: \"@riotcodes\", href: \"https://x.com/riotcodes\" },\n    { label: \"Read.cv\", handle: \"read.cv/riot\", href: \"https://read.cv/riot\" },\n  ] as const,\n} as const\n\nexport const footer = {\n  blurb:\n    \"RIOT is the working name of Marcus Vega, a full-stack developer and creative technologist in Brooklyn. This is a demo of the street-code portfolio template — every word on it lives in one file.\",\n} as const\n","type":"registry:lib"},{"path":"app/street-code/components/site-header.tsx","target":"app/street-code/components/site-header.tsx","content":"\"use client\"\n\nimport { useRef } from \"react\"\n\nimport { BASE, brand, nav } from \"../data/content\"\n\n/**\n * The header — a sprayed tag, the handle, and the nav.\n *\n * This is a one-page scroll, so the nav points at in-page anchors (`#work`) and\n * there is no active-route logic to run. The only reason it is a client component\n * is the mobile menu: it is a native `<details>`, and a tap on a link inside it\n * should close it before the page scrolls. One ref, one handler, no state.\n */\nexport function SiteHeader() {\n  const menu = useRef<HTMLDetailsElement>(null)\n\n  const closeMenu = () => {\n    if (menu.current) menu.current.open = false\n  }\n\n  return (\n    <header className=\"scHeader\">\n      <div className=\"scWrap scHeaderRow\">\n        <a className=\"scBrand\" href={BASE || \"/\"}>\n          <span className=\"scBrandTag\">{brand.tag}</span>\n          <span className=\"scBrandHandle\">{brand.handle}</span>\n        </a>\n\n        <nav className=\"scNav\" aria-label=\"Primary\">\n          {nav.map((link) => (\n            <a key={link.href} href={link.href} className=\"scNavLink\">\n              {link.label}\n            </a>\n          ))}\n          <a href=\"#contact\" className=\"scNavCta\">\n            Hire me\n          </a>\n        </nav>\n\n        <details className=\"scMenu\" ref={menu}>\n          <summary className=\"scMenuSummary\" aria-label=\"Menu\">\n            <span className=\"scMenuBar\" aria-hidden=\"true\" />\n            <span className=\"scMenuBar\" aria-hidden=\"true\" />\n            <span className=\"scMenuBar\" aria-hidden=\"true\" />\n          </summary>\n          <nav className=\"scMenuPanel\" aria-label=\"Primary\">\n            {nav.map((link) => (\n              <a key={link.href} href={link.href} className=\"scMenuLink\" onClick={closeMenu}>\n                {link.label}\n              </a>\n            ))}\n            <a href=\"#contact\" className=\"scMenuLink scMenuLinkCta\" onClick={closeMenu}>\n              Hire me\n            </a>\n          </nav>\n        </details>\n      </div>\n    </header>\n  )\n}\n","type":"registry:component"},{"path":"app/street-code/components/site-footer.tsx","target":"app/street-code/components/site-footer.tsx","content":"import { brand, footer, nav } from \"../data/content\"\n\n/**\n * The footer, closing on the oversized tag that is meant to be clipped by the\n * page edge — the way a name runs off the end of a wall. Server component; there\n * is nothing here to hydrate.\n */\nexport function SiteFooter() {\n  return (\n    <footer className=\"scFooter\">\n      <div className=\"scWrap\">\n        <div className=\"scFooterTop\">\n          <p className=\"scFooterBlurb\">{footer.blurb}</p>\n\n          <nav className=\"scFooterNav\" aria-label=\"Footer\">\n            {nav.map((link) => (\n              <a key={link.href} href={link.href} className=\"scFooterLink\">\n                {link.label}\n              </a>\n            ))}\n            <a href={`mailto:${brand.email}`} className=\"scFooterLink\">\n              Email\n            </a>\n          </nav>\n        </div>\n\n        <div className=\"scFooterBottom\">\n          <span>\n            © {brand.year} {brand.name} — {brand.city}\n          </span>\n          <a href={`mailto:${brand.email}`}>{brand.email}</a>\n        </div>\n      </div>\n\n      {/* Runs full-bleed and off both edges. The clip is the effect. */}\n      <p className=\"scFooterTag\" aria-hidden=\"true\">\n        {brand.tag}\n      </p>\n    </footer>\n  )\n}\n","type":"registry:component"},{"path":"app/street-code/components/reveal.tsx","target":"app/street-code/components/reveal.tsx","content":"\"use client\"\n\nimport { useEffect, useRef, useState } from \"react\"\n\ntype RevealProps = {\n  children: React.ReactNode\n  /** Merged after `scReveal`, so this is where the layout class goes. */\n  className?: string\n  /** Stagger, in milliseconds. Feeds `--sc-delay` in the stylesheet. */\n  delay?: number\n}\n\n/**\n * Fades and lifts its children in when they first scroll into view.\n *\n * It renders a plain `<div>` and merges `className`, so the revealed element *is*\n * the layout element — `<Reveal className=\"scCard\">` is a grid child, not a\n * wrapper that breaks the grid. Modelled on the monolith-launch Reveal, with the\n * same three deliberate choices:\n *\n *   - The observer disconnects on first intersection. Re-hiding content the\n *     reader has already passed is motion for its own sake, and a long page keeps\n *     zero observers alive by the time you reach the footer.\n *   - The hidden state lives in CSS, not here, so nothing flashes in before\n *     hydration and `prefers-reduced-motion` can switch it all off.\n *   - If `IntersectionObserver` is missing, the content shows immediately.\n *     Failing open is the only acceptable direction for a fade-in.\n *\n * `data-shown` also drives the marker doodles inside a revealed block: their\n * stroke draws in on the same flip (see `.scReveal[data-shown=\"true\"]` in the\n * stylesheet).\n */\nexport function Reveal({ children, className, delay = 0 }: RevealProps) {\n  const ref = useRef<HTMLDivElement>(null)\n  const [shown, setShown] = useState(false)\n\n  useEffect(() => {\n    const node = ref.current\n    if (!node || shown) return\n\n    if (typeof IntersectionObserver === \"undefined\") {\n      setShown(true)\n      return\n    }\n\n    const observer = new IntersectionObserver(\n      (entries) => {\n        if (!entries.some((entry) => entry.isIntersecting)) return\n        setShown(true)\n        observer.disconnect()\n      },\n      /* Held back from the bottom edge so it reads as a reveal, not a pop. */\n      { rootMargin: \"0px 0px -10% 0px\", threshold: 0.05 },\n    )\n\n    observer.observe(node)\n    return () => observer.disconnect()\n  }, [shown])\n\n  return (\n    <div\n      ref={ref}\n      className={className ? `scReveal ${className}` : \"scReveal\"}\n      data-shown={shown ? \"true\" : \"false\"}\n      style={delay ? ({ \"--sc-delay\": `${delay}ms` } as React.CSSProperties) : undefined}\n    >\n      {children}\n    </div>\n  )\n}\n","type":"registry:component"},{"path":"app/street-code/components/ticker.tsx","target":"app/street-code/components/ticker.tsx","content":"import { marquee } from \"../data/content\"\n\n/**\n * The spray-paint marquee under the hero.\n *\n * The list is rendered twice because `@keyframes scMarquee` translates the track\n * by exactly -50% — with one copy the loop would jump at the seam. The second\n * copy is `aria-hidden`, so a screen reader hears each phrase once, not twice.\n *\n * A `prefers-reduced-motion` rule in the stylesheet stops the animation and lets\n * the row scroll on overflow instead, so the phrases are still all reachable.\n */\nexport function Ticker() {\n  return (\n    <div className=\"scMarquee\" aria-label=\"What I'm about\">\n      <div className=\"scMarqueeTrack\">\n        {marquee.map((line) => (\n          <span key={line} className=\"scMarqueeItem\">\n            {line}\n          </span>\n        ))}\n        {marquee.map((line) => (\n          <span key={`echo-${line}`} className=\"scMarqueeItem\" aria-hidden=\"true\">\n            {line}\n          </span>\n        ))}\n      </div>\n    </div>\n  )\n}\n","type":"registry:component"},{"path":"app/street-code/components/doodles.tsx","target":"app/street-code/components/doodles.tsx","content":"/**\n * Hand-drawn graffiti marks, in inline SVG.\n *\n * These are the whole theme. A dark page with a green button is a dark page with\n * a green button; the crown over the headline, the marker loop around a word and\n * the scribbled underline are what make it read as street art. So they are drawn\n * by hand here rather than pulled from an icon set — every path is a little\n * irregular on purpose, because a perfect circle looks like a border, not paint.\n *\n * Each mark is presentational and `aria-hidden`, takes a `className` for\n * placement, and paints in `currentColor` so the accent colour is set by CSS.\n *\n * The line-drawing marks (`Underline`, `CircleLoop`) use `pathLength={1}` so the\n * stylesheet can animate the stroke on a 0..1 scale without knowing the real path\n * length. The CSS draws them in when a `data-shown` ancestor flips, or immediately\n * when they carry `scDrawNow`.\n */\n\ntype MarkProps = {\n  className?: string\n}\n\n/** The Basquiat crown. Goes above and to the side of the headline. */\nexport function Crown({ className }: MarkProps) {\n  return (\n    <svg\n      className={className}\n      viewBox=\"0 0 120 80\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth={7}\n      strokeLinejoin=\"round\"\n      strokeLinecap=\"round\"\n      aria-hidden=\"true\"\n      focusable=\"false\"\n    >\n      <path pathLength={1} d=\"M8 68 L16 20 L40 50 L60 12 L80 50 L104 20 L112 68 Z\" />\n      {/* Jewels — three quick dabs across the band. */}\n      <path pathLength={1} d=\"M34 60 L36 60\" />\n      <path pathLength={1} d=\"M60 58 L62 58\" />\n      <path pathLength={1} d=\"M86 60 L88 60\" />\n    </svg>\n  )\n}\n\n/** A loose marker loop, meant to circle a single word. Draws in. */\nexport function CircleLoop({ className }: MarkProps) {\n  return (\n    <svg\n      className={className}\n      viewBox=\"0 0 320 140\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth={6}\n      strokeLinecap=\"round\"\n      aria-hidden=\"true\"\n      focusable=\"false\"\n    >\n      {/* Overshoots past the start, the way a hand does — never a clean join. */}\n      <path\n        pathLength={1}\n        d=\"M228 18 C120 -8 30 20 20 68 C12 108 96 128 178 126 C268 124 320 96 306 58 C296 30 232 16 150 20\"\n      />\n    </svg>\n  )\n}\n\n/** A double-stroke marker underline. The hero and section headings sit on it. */\nexport function Underline({ className }: MarkProps) {\n  return (\n    <svg\n      className={className}\n      viewBox=\"0 0 340 36\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeLinecap=\"round\"\n      aria-hidden=\"true\"\n      focusable=\"false\"\n    >\n      {/* Heavy first pass. */}\n      <path pathLength={1} strokeWidth={9} d=\"M8 18 C90 8 210 8 332 16\" />\n      {/* A lighter second pass, offset — the classic marker double-take. */}\n      <path\n        pathLength={1}\n        strokeWidth={4}\n        d=\"M14 27 C120 22 220 24 320 25\"\n        className=\"scUnderlineSecond\"\n      />\n    </svg>\n  )\n}\n\n/** A curved arrow with a marker head. Points from a scribbled note to its target. */\nexport function Arrow({ className }: MarkProps) {\n  return (\n    <svg\n      className={className}\n      viewBox=\"0 0 130 120\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth={6}\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      aria-hidden=\"true\"\n      focusable=\"false\"\n    >\n      <path pathLength={1} d=\"M18 12 C70 18 108 44 100 96\" />\n      {/* Arrowhead. */}\n      <path pathLength={1} d=\"M78 82 L100 100 L118 74\" />\n    </svg>\n  )\n}\n\n/** A four-point spark / star burst. A quick accent near numbers and CTAs. */\nexport function Spark({ className }: MarkProps) {\n  return (\n    <svg\n      className={className}\n      viewBox=\"0 0 60 60\"\n      fill=\"currentColor\"\n      aria-hidden=\"true\"\n      focusable=\"false\"\n    >\n      <path d=\"M30 0 C33 20 40 27 60 30 C40 33 33 40 30 60 C27 40 20 33 0 30 C20 27 27 20 30 0 Z\" />\n    </svg>\n  )\n}\n\n/** A scribbled bracket, like a hand-drawn corner mark. Frames the portrait. */\nexport function CornerScribble({ className }: MarkProps) {\n  return (\n    <svg\n      className={className}\n      viewBox=\"0 0 100 100\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth={6}\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      aria-hidden=\"true\"\n      focusable=\"false\"\n    >\n      <path d=\"M6 40 C4 22 10 8 30 6 C46 5 58 9 70 8\" />\n      <path d=\"M12 34 C11 24 16 16 28 15\" opacity={0.55} />\n    </svg>\n  )\n}\n","type":"registry:component"}],"assets":[{"url":"https://ui.artbloom.tech/r/asset/templates/street-code/assets/portrait.png","target":"public/street-code/portrait.png","bytes":1238891}],"meta":{"kind":"templates","categories":["portfolio","personal"],"docs":"https://ui.artbloom.tech/artbloom/templates/street-code"}}