mirror of
https://github.com/Nutlope/hallmark.git
synced 2026-08-14 12:35:33 +02:00
Image hook (Together) + canonical examples for the 4 new themes
- scripts/imagegen.mjs: opt-in generated-still hook via Together's images
endpoint (FLUX.1.1-pro default, gemini-3-pro-image optional), cache by
prompt hash, provenance sidecar. No key -> graceful exit 3, falls back
to CSS-art / placeholder (verified). Wired into tier-e-generated.md +
SKILL Step 4 as opt-in, never auto-fires.
- site/examples/{grid,field,ledger,arcade}-01: canonical builds from the
proposal mockups, all sloplint 0-FAIL, referenced by each theme spec.
This commit is contained in:
@@ -0,0 +1,438 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Summer Jam 26 · a 48-hour game jam for people with day jobs</title>
|
||||
<meta name="description" content="Summer Jam 26 is a 48-hour game jam for people with day jobs. One theme, teams of any size, free to enter." />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@400;500;600;700;800&family=Silkscreen:wght@400;700&display=swap" rel="stylesheet" />
|
||||
<style>
|
||||
/* Hallmark · macrostructure: Manifesto · theme: Arcade (candidate, alternate) · tone: retro-nostalgia/playful */
|
||||
|
||||
:root {
|
||||
/* palette - oklch only, paired accent duo on violet-black */
|
||||
--paper: oklch(16% 0.02 300);
|
||||
--paper-2: oklch(21% 0.03 300);
|
||||
--paper-3: oklch(26% 0.035 300);
|
||||
--ink: oklch(95% 0.012 300);
|
||||
--ink-dim: oklch(80% 0.02 300);
|
||||
--ink-faint: oklch(66% 0.03 300);
|
||||
--magenta: oklch(70% 0.24 340);
|
||||
--cyan: oklch(85% 0.13 195);
|
||||
|
||||
/* lines, overlays, shadows (chroma >= 0.005 or alpha < 1) */
|
||||
--line: oklch(38% 0.03 300);
|
||||
--line-lit: oklch(52% 0.05 320);
|
||||
--star: oklch(95% 0.012 300 / 0.9);
|
||||
--star-dim: oklch(82% 0.02 300 / 0.5);
|
||||
--star-cyan: oklch(85% 0.13 195 / 0.72);
|
||||
--scanline: oklch(9% 0.02 300 / 0.5);
|
||||
--shadow-mag: oklch(46% 0.15 342);
|
||||
--shadow-cyn: oklch(50% 0.09 200);
|
||||
--ring: oklch(85% 0.13 195);
|
||||
|
||||
/* type */
|
||||
--font-display: "Silkscreen", monospace;
|
||||
--font-body: "Hanken Grotesk", sans-serif;
|
||||
|
||||
/* 4pt spacing scale */
|
||||
--space-1: 0.25rem; --space-2: 0.5rem; --space-3: 0.75rem;
|
||||
--space-4: 1rem; --space-5: 1.25rem; --space-6: 1.5rem;
|
||||
--space-8: 2rem; --space-10: 2.5rem; --space-12: 3rem;
|
||||
--space-16: 4rem; --space-20: 5rem;
|
||||
|
||||
--maxw: 72rem;
|
||||
}
|
||||
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
html, body { overflow-x: clip; }
|
||||
|
||||
body {
|
||||
background-color: var(--paper);
|
||||
background-image:
|
||||
radial-gradient(1.5px 1.5px at 12% 16%, var(--star), transparent 62%),
|
||||
radial-gradient(1.5px 1.5px at 83% 24%, var(--star-cyan), transparent 62%),
|
||||
radial-gradient(1px 1px at 46% 62%, var(--star-dim), transparent 62%),
|
||||
radial-gradient(1.5px 1.5px at 68% 80%, var(--star), transparent 62%),
|
||||
radial-gradient(1px 1px at 26% 84%, var(--star-dim), transparent 62%),
|
||||
radial-gradient(1px 1px at 93% 66%, var(--star-cyan), transparent 62%),
|
||||
radial-gradient(1.5px 1.5px at 7% 52%, var(--star-dim), transparent 62%),
|
||||
radial-gradient(1px 1px at 57% 30%, var(--star-dim), transparent 62%);
|
||||
background-attachment: fixed;
|
||||
background-repeat: no-repeat;
|
||||
color: var(--ink);
|
||||
font-family: var(--font-body);
|
||||
font-size: 1rem;
|
||||
line-height: 1.6;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
letter-spacing: 0.005em;
|
||||
}
|
||||
|
||||
/* CRT scanline overlay - decorative pseudo-element, never blocks input */
|
||||
body::after {
|
||||
content: "";
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 40;
|
||||
pointer-events: none;
|
||||
background: repeating-linear-gradient(
|
||||
to bottom,
|
||||
transparent 0, transparent 2px,
|
||||
var(--scanline) 2px, var(--scanline) 3px
|
||||
);
|
||||
}
|
||||
|
||||
.wrap { max-width: var(--maxw); margin: 0 auto; padding: 0 clamp(20px, 5vw, 56px); }
|
||||
|
||||
/* skip link */
|
||||
.skip {
|
||||
position: absolute; left: var(--space-2); top: calc(-1 * var(--space-16));
|
||||
z-index: 60; background: var(--cyan); color: var(--paper);
|
||||
font-family: var(--font-display); font-size: 0.7rem;
|
||||
padding: var(--space-2) var(--space-3); text-decoration: none;
|
||||
}
|
||||
.skip:focus { top: var(--space-2); }
|
||||
|
||||
.sr-only {
|
||||
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
|
||||
overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
|
||||
}
|
||||
|
||||
/* pixel display + labels */
|
||||
.pix { font-family: var(--font-display); font-weight: 700; line-height: 1.1; }
|
||||
|
||||
/* ---------- top strip ---------- */
|
||||
.strip { position: relative; z-index: 1; border-bottom: 2px solid var(--line); }
|
||||
.strip__row {
|
||||
display: flex; align-items: center; gap: var(--space-3);
|
||||
padding-top: var(--space-3); padding-bottom: var(--space-3);
|
||||
font-family: var(--font-display); font-size: 0.66rem; line-height: 1.2;
|
||||
letter-spacing: 0.02em; flex-wrap: wrap;
|
||||
}
|
||||
.strip__tag { color: var(--cyan); }
|
||||
.strip__tag--ink { color: var(--ink); }
|
||||
.strip__tag--faint { color: var(--ink-faint); }
|
||||
.strip__dot { color: var(--line-lit); }
|
||||
.strip__spacer { margin-left: auto; }
|
||||
|
||||
/* ---------- hero ---------- */
|
||||
.hero { position: relative; z-index: 1; }
|
||||
.hero__inner {
|
||||
display: flex; flex-direction: column; align-items: flex-start;
|
||||
min-height: 82svh;
|
||||
padding-top: var(--space-10); padding-bottom: var(--space-20);
|
||||
}
|
||||
.hero__kicker {
|
||||
font-family: var(--font-display); font-size: 0.78rem; line-height: 1.2;
|
||||
color: var(--cyan); letter-spacing: 0.04em; margin-bottom: var(--space-5);
|
||||
}
|
||||
.hero__display {
|
||||
font-family: var(--font-display); font-weight: 700;
|
||||
font-size: clamp(2.25rem, 8.5vw, 5.25rem); line-height: 1.05;
|
||||
color: var(--ink); letter-spacing: 0.01em;
|
||||
text-shadow: 0.085em 0.085em 0 var(--magenta);
|
||||
overflow-wrap: anywhere; min-width: 0; margin-bottom: var(--space-6);
|
||||
}
|
||||
.hero__line { display: block; }
|
||||
.cursor {
|
||||
display: inline-block; width: 0.58em; height: 0.86em;
|
||||
margin-left: 0.14em; translate: 0 0.06em;
|
||||
background: var(--cyan);
|
||||
box-shadow: 0.09em 0.09em 0 var(--magenta);
|
||||
animation: cursor-blink 1.06s steps(1) infinite;
|
||||
}
|
||||
@keyframes cursor-blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }
|
||||
|
||||
.hero__sub {
|
||||
font-size: clamp(1rem, 1.6vw, 1.2rem); line-height: 1.55;
|
||||
color: var(--ink-dim); max-width: 54ch; margin-bottom: var(--space-8);
|
||||
}
|
||||
|
||||
/* ---------- score-strip (player select) ---------- */
|
||||
.score {
|
||||
display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: var(--space-4); width: 100%; max-width: 40rem;
|
||||
}
|
||||
.score__slot {
|
||||
display: flex; flex-direction: column; gap: var(--space-1);
|
||||
padding: var(--space-4) var(--space-5);
|
||||
background: var(--paper-2); color: var(--ink);
|
||||
border: 2px solid var(--line); text-decoration: none;
|
||||
box-shadow: 5px 5px 0 var(--shadow-cyn);
|
||||
transition: transform 120ms ease-out, box-shadow 120ms ease-out, border-color 120ms ease-out;
|
||||
}
|
||||
.score__player { font-family: var(--font-display); font-size: 0.72rem; line-height: 1.2; }
|
||||
.score__action { font-family: var(--font-display); font-size: 1.15rem; line-height: 1.1; color: var(--ink); }
|
||||
.score__meta { font-size: 0.82rem; color: var(--ink-faint); letter-spacing: 0.02em; }
|
||||
.score__slot--p1 { box-shadow: 5px 5px 0 var(--shadow-mag); }
|
||||
.score__slot--p1 .score__player { color: var(--magenta); }
|
||||
.score__slot--p2 .score__player { color: var(--cyan); }
|
||||
.score__slot:hover { transform: translate(-2px, -2px); box-shadow: 8px 8px 0 var(--shadow-cyn); border-color: var(--line-lit); }
|
||||
.score__slot--p1:hover { box-shadow: 8px 8px 0 var(--shadow-mag); }
|
||||
.score__slot:active { transform: translate(3px, 3px); box-shadow: 2px 2px 0 var(--shadow-cyn); }
|
||||
.score__slot--p1:active { box-shadow: 2px 2px 0 var(--shadow-mag); }
|
||||
.score__slot:focus-visible { outline: 3px solid var(--ring); outline-offset: 4px; }
|
||||
|
||||
/* ---------- generic section head ---------- */
|
||||
.section { position: relative; z-index: 1; padding-top: clamp(var(--space-16), 9vw, var(--space-20)); padding-bottom: clamp(var(--space-16), 9vw, var(--space-20)); border-top: 2px solid var(--line); }
|
||||
.sec__kicker { font-family: var(--font-display); font-size: 0.74rem; line-height: 1.2; color: var(--cyan); letter-spacing: 0.03em; margin-bottom: var(--space-3); }
|
||||
.sec__kicker--mag { color: var(--magenta); }
|
||||
.sec__title { font-family: var(--font-body); font-weight: 800; font-size: clamp(1.6rem, 3.4vw, 2.5rem); line-height: 1.1; letter-spacing: -0.01em; color: var(--ink); max-width: 20ch; overflow-wrap: anywhere; min-width: 0; }
|
||||
.sec__lede { margin-top: var(--space-4); font-size: 1.05rem; color: var(--ink-dim); max-width: 58ch; }
|
||||
.sec__gap { margin-top: clamp(var(--space-12), 6vw, var(--space-16)); }
|
||||
|
||||
/* ---------- how it works (three beats) ---------- */
|
||||
.beats { list-style: none; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-6); margin-top: var(--space-10); }
|
||||
.beat { padding-top: var(--space-5); border-top: 2px solid var(--line-lit); }
|
||||
.beat__num { font-family: var(--font-display); font-size: 1.6rem; line-height: 1; color: var(--magenta); }
|
||||
.beat:nth-child(2) .beat__num { color: var(--cyan); }
|
||||
.beat:nth-child(3) .beat__num { color: var(--magenta); }
|
||||
.beat__name { font-family: var(--font-body); font-weight: 700; font-size: 1.15rem; line-height: 1.2; color: var(--ink); margin-top: var(--space-3); }
|
||||
.beat__body { margin-top: var(--space-3); color: var(--ink-dim); font-size: 0.98rem; }
|
||||
|
||||
/* ---------- schedule ---------- */
|
||||
.table-wrap { margin-top: var(--space-10); overflow-x: auto; border: 2px solid var(--line); }
|
||||
.timetable { width: 100%; border-collapse: collapse; min-width: 34rem; }
|
||||
.timetable caption { text-align: left; }
|
||||
.timetable th {
|
||||
font-family: var(--font-display); font-size: 0.7rem; line-height: 1.3; font-weight: 700;
|
||||
color: var(--cyan); text-align: left; letter-spacing: 0.03em;
|
||||
padding: var(--space-3) var(--space-4); border-bottom: 2px solid var(--line-lit);
|
||||
}
|
||||
.timetable td { padding: var(--space-4); vertical-align: top; border-bottom: 1px solid var(--line); font-size: 0.96rem; color: var(--ink-dim); }
|
||||
.timetable tbody tr:last-child td { border-bottom: 0; }
|
||||
.timetable tbody tr:nth-child(even) { background: var(--paper-2); }
|
||||
.t-when { font-family: var(--font-display); font-size: 0.74rem; line-height: 1.4; color: var(--ink); white-space: nowrap; }
|
||||
.t-slot { font-family: var(--font-display); font-size: 0.74rem; line-height: 1.4; color: var(--magenta); white-space: nowrap; }
|
||||
|
||||
/* ---------- tracks + prizes ---------- */
|
||||
.track-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-4); margin-top: var(--space-10); }
|
||||
.track { padding: var(--space-6); background: var(--paper-2); color: var(--ink); border: 2px solid var(--line); }
|
||||
.track__tag { display: inline-block; font-family: var(--font-display); font-size: 0.72rem; line-height: 1.2; padding: var(--space-2) var(--space-3); color: var(--paper); background: var(--cyan); }
|
||||
.track:nth-child(even) .track__tag { background: var(--magenta); }
|
||||
.track__body { margin-top: var(--space-4); color: var(--ink-dim); font-size: 0.98rem; }
|
||||
|
||||
.prize-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-4); margin-top: var(--space-8); }
|
||||
.prize { padding: var(--space-6); border: 2px solid var(--line-lit); border-top-width: 6px; }
|
||||
.prize__rank { font-family: var(--font-display); font-size: 1.1rem; line-height: 1; color: var(--cyan); }
|
||||
.prize:nth-child(2) { border-top-color: var(--magenta); }
|
||||
.prize:nth-child(2) .prize__rank { color: var(--magenta); }
|
||||
.prize__name { font-family: var(--font-body); font-weight: 800; font-size: 1.1rem; line-height: 1.15; color: var(--ink); margin-top: var(--space-3); }
|
||||
.prize__body { margin-top: var(--space-2); color: var(--ink-dim); font-size: 0.94rem; }
|
||||
|
||||
/* ---------- register call-out ---------- */
|
||||
.coin__inner { display: flex; flex-direction: column; align-items: flex-start; }
|
||||
.coin__title { font-family: var(--font-display); font-weight: 700; font-size: clamp(2rem, 6vw, 3.75rem); line-height: 1.08; color: var(--ink); text-shadow: 0.07em 0.07em 0 var(--magenta); overflow-wrap: anywhere; min-width: 0; margin-top: var(--space-2); }
|
||||
.coin__sub { margin-top: var(--space-6); font-size: 1.1rem; color: var(--ink-dim); max-width: 52ch; }
|
||||
.btn {
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
margin-top: var(--space-8); font-family: var(--font-display); font-weight: 700;
|
||||
font-size: clamp(1rem, 2.4vw, 1.4rem); line-height: 1.1;
|
||||
color: var(--paper); background: var(--magenta);
|
||||
padding: var(--space-4) var(--space-8); border: 2px solid var(--ink);
|
||||
text-decoration: none; white-space: nowrap;
|
||||
box-shadow: 6px 6px 0 var(--shadow-cyn);
|
||||
transition: transform 110ms ease-out, box-shadow 110ms ease-out;
|
||||
}
|
||||
.btn:hover { transform: translate(-3px, -3px); box-shadow: 9px 9px 0 var(--shadow-cyn); }
|
||||
.btn:active { transform: translate(3px, 3px); box-shadow: 2px 2px 0 var(--shadow-cyn); }
|
||||
.btn:focus-visible { outline: 3px solid var(--ring); outline-offset: 5px; }
|
||||
.coin__fine { margin-top: var(--space-6); font-family: var(--font-display); font-size: 0.68rem; line-height: 1.6; color: var(--ink-faint); letter-spacing: 0.02em; }
|
||||
|
||||
/* ---------- footer high-score board ---------- */
|
||||
.foot { position: relative; z-index: 1; padding-top: clamp(var(--space-16), 9vw, var(--space-20)); padding-bottom: var(--space-16); background: var(--paper-2); color: var(--ink); border-top: 2px solid var(--line-lit); }
|
||||
.scores { width: 100%; border-collapse: collapse; margin-top: var(--space-6); max-width: 40rem; }
|
||||
.scores td { padding: var(--space-3) var(--space-4); border-bottom: 1px solid var(--line); font-size: 0.98rem; color: var(--ink-dim); }
|
||||
.scores tr:last-child td { border-bottom: 0; }
|
||||
.rank { font-family: var(--font-display); font-size: 0.72rem; line-height: 1.4; color: var(--cyan); white-space: nowrap; width: 1%; }
|
||||
.init { font-family: var(--font-display); font-size: 0.72rem; line-height: 1.4; color: var(--magenta); white-space: nowrap; width: 1%; }
|
||||
.foot__meta { margin-top: var(--space-12); display: flex; flex-direction: column; gap: var(--space-2); }
|
||||
.foot__brand { font-family: var(--font-display); font-size: 1rem; line-height: 1.2; color: var(--ink); }
|
||||
.foot__note { color: var(--ink-dim); max-width: 48ch; }
|
||||
.foot__legal { margin-top: var(--space-2); font-family: var(--font-display); font-size: 0.64rem; line-height: 1.6; color: var(--ink-faint); letter-spacing: 0.02em; }
|
||||
|
||||
/* ---------- responsive ---------- */
|
||||
@media (max-width: 48rem) {
|
||||
.beats { grid-template-columns: minmax(0, 1fr); gap: var(--space-8); }
|
||||
.prize-grid { grid-template-columns: minmax(0, 1fr); }
|
||||
.track-grid { grid-template-columns: minmax(0, 1fr); }
|
||||
}
|
||||
@media (max-width: 30rem) {
|
||||
.score { grid-template-columns: minmax(0, 1fr); }
|
||||
.strip__spacer { margin-left: 0; flex-basis: 100%; height: 0; }
|
||||
}
|
||||
|
||||
/* ---------- reduced motion ---------- */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.cursor { animation: none; opacity: 1; }
|
||||
*, *::before, *::after { animation: none !important; transition: none !important; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<a class="skip" href="#main">Skip to content</a>
|
||||
|
||||
<header class="strip">
|
||||
<div class="wrap strip__row">
|
||||
<span class="strip__tag">FREE PLAY</span>
|
||||
<span class="strip__dot" aria-hidden="true">·</span>
|
||||
<span class="strip__tag--ink">EVENT 07</span>
|
||||
<span class="strip__spacer" aria-hidden="true"></span>
|
||||
<span class="strip__tag--faint">SELECT / 1 CREDIT</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main id="main">
|
||||
|
||||
<section class="hero">
|
||||
<div class="wrap hero__inner">
|
||||
<p class="hero__kicker">NOW ACCEPTING PLAYERS</p>
|
||||
<h1 class="hero__display">
|
||||
<span class="hero__line">SUMMER</span>
|
||||
<span class="hero__line">JAM 26<span class="cursor" aria-hidden="true"></span></span>
|
||||
</h1>
|
||||
<p class="hero__sub">A 48-hour game jam for people with day jobs. One theme drops Friday night, teams of any size ship by Sunday, and Monday you go back to your inbox like nothing happened.</p>
|
||||
<div class="score" role="group" aria-label="Register or sponsor">
|
||||
<a class="score__slot score__slot--p1" href="#register">
|
||||
<span class="score__player">P1</span>
|
||||
<span class="score__action">REGISTER</span>
|
||||
<span class="score__meta">Free entry, grab a slot</span>
|
||||
</a>
|
||||
<a class="score__slot score__slot--p2" href="#sponsor">
|
||||
<span class="score__player">P2</span>
|
||||
<span class="score__action">SPONSOR</span>
|
||||
<span class="score__meta">Back the after-hours crew</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="how-title">
|
||||
<div class="wrap">
|
||||
<p class="sec__kicker">HOW THE WEEKEND RUNS</p>
|
||||
<h2 class="sec__title" id="how-title">Three moves, one weekend</h2>
|
||||
<p class="sec__lede">No pre-built engines, no head start. Everybody opens the same blank screen at the same minute and figures it out from there.</p>
|
||||
<ol class="beats">
|
||||
<li class="beat">
|
||||
<span class="beat__num">01</span>
|
||||
<h3 class="beat__name">Theme drops</h3>
|
||||
<p class="beat__body">Friday at 6pm the theme goes live. You find out what you are making at the exact moment everyone else does.</p>
|
||||
</li>
|
||||
<li class="beat">
|
||||
<span class="beat__num">02</span>
|
||||
<h3 class="beat__name">Build all weekend</h3>
|
||||
<p class="beat__body">48 hours to make something playable. Sleep is optional, snacks are not, and the help pit stays open the whole time.</p>
|
||||
</li>
|
||||
<li class="beat">
|
||||
<span class="beat__num">03</span>
|
||||
<h3 class="beat__name">Ship and play</h3>
|
||||
<p class="beat__body">Sunday at 6pm hands come off the keyboard. Every team demos, everyone plays everyone, and the room picks its favorites.</p>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="sked-title">
|
||||
<div class="wrap">
|
||||
<p class="sec__kicker sec__kicker--mag">RUN OF SHOW</p>
|
||||
<h2 class="sec__title" id="sked-title">The 48-hour clock</h2>
|
||||
<div class="table-wrap">
|
||||
<table class="timetable">
|
||||
<caption class="sr-only">Summer Jam 26 weekend schedule</caption>
|
||||
<thead>
|
||||
<tr><th scope="col">When</th><th scope="col">Slot</th><th scope="col">What happens</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td class="t-when">FRI 18:00</td><td class="t-slot">KICKOFF</td><td>Theme reveal, the ground rules, and the starting buzzer.</td></tr>
|
||||
<tr><td class="t-when">FRI 19:30</td><td class="t-slot">TEAM UP</td><td>Find collaborators or commit to flying solo. Any size counts.</td></tr>
|
||||
<tr><td class="t-when">SAT ALL DAY</td><td class="t-slot">BUILD</td><td>Heads down. Mentors roam the floor, coffee keeps flowing.</td></tr>
|
||||
<tr><td class="t-when">SUN 15:00</td><td class="t-slot">SUBMIT</td><td>Tools down, upload the build, and write a readme humans can follow.</td></tr>
|
||||
<tr><td class="t-when">SUN 18:00</td><td class="t-slot">OPEN PLAY</td><td>The arcade opens. Everyone plays everything, everyone votes.</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="tracks-title">
|
||||
<div class="wrap">
|
||||
<p class="sec__kicker">PICK YOUR TRACK</p>
|
||||
<h2 class="sec__title" id="tracks-title">Four ways to enter</h2>
|
||||
<div class="track-grid">
|
||||
<article class="track">
|
||||
<span class="track__tag">SOLO RUN</span>
|
||||
<p class="track__body">One person, one build, start to finish. Prove you do not need a team to make something worth playing.</p>
|
||||
</article>
|
||||
<article class="track">
|
||||
<span class="track__tag">COUCH CO-OP</span>
|
||||
<p class="track__body">Two to five people sharing a table, a whiteboard, and a deadline. The classic weekend crew.</p>
|
||||
</article>
|
||||
<article class="track">
|
||||
<span class="track__tag">THEME TAKEOVER</span>
|
||||
<p class="track__body">Lean the hardest into the theme. Judged purely on how far you push the prompt. Weird is rewarded.</p>
|
||||
</article>
|
||||
<article class="track">
|
||||
<span class="track__tag">ROOKIE CABINET</span>
|
||||
<p class="track__body">First jam ever? This track is scored on its own curve so a debut never competes against a veteran.</p>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div class="sec__gap">
|
||||
<p class="sec__kicker sec__kicker--mag">ON THE MARQUEE</p>
|
||||
<h2 class="sec__title">What you are playing for</h2>
|
||||
<div class="prize-grid">
|
||||
<article class="prize">
|
||||
<span class="prize__rank">1UP</span>
|
||||
<h3 class="prize__name">Best in show</h3>
|
||||
<p class="prize__body">The build the whole room crowds around. Overall pick of the weekend.</p>
|
||||
</article>
|
||||
<article class="prize">
|
||||
<span class="prize__rank">2UP</span>
|
||||
<h3 class="prize__name">Crowd favorite</h3>
|
||||
<p class="prize__body">Chosen by every player in the arcade, not by a panel behind a curtain.</p>
|
||||
</article>
|
||||
<article class="prize">
|
||||
<span class="prize__rank">3UP</span>
|
||||
<h3 class="prize__name">Tech flex</h3>
|
||||
<p class="prize__body">For the clever hack under the hood that nobody in the room saw coming.</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="register" aria-labelledby="cta-title">
|
||||
<div class="wrap coin__inner">
|
||||
<p class="sec__kicker">INSERT COIN</p>
|
||||
<h2 class="coin__title" id="cta-title">Ready<br>player 1?</h2>
|
||||
<p class="coin__sub">Slots are free and the theme stays secret until the buzzer. Grab yours, wrangle a team or do not, and clear one weekend on the calendar.</p>
|
||||
<a class="btn" href="#register">Register your team</a>
|
||||
<p class="coin__fine">48 HOURS · ANY TEAM SIZE · ONE THEME · FREE TO ENTER</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<footer class="foot">
|
||||
<div class="wrap">
|
||||
<p class="sec__kicker">HIGH SCORES</p>
|
||||
<table class="scores">
|
||||
<caption class="sr-only">What Summer Jam 26 stands for</caption>
|
||||
<tbody>
|
||||
<tr><td class="rank">1ST</td><td class="init">JAM</td><td>Free play. No entry fee, no catch, ever.</td></tr>
|
||||
<tr><td class="rank">2ND</td><td class="init">DEV</td><td>48 hours on the clock, one shared theme.</td></tr>
|
||||
<tr><td class="rank">3RD</td><td class="init">FUN</td><td>Teams of any size, rookies always welcome.</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="foot__meta">
|
||||
<p class="foot__brand">SUMMER JAM 26</p>
|
||||
<p class="foot__note">A 48-hour game jam for people with day jobs. Built for the crowd that codes after the kids are asleep.</p>
|
||||
<p class="foot__legal">FREE PLAY · EVENT 07 · MADE FOR THE AFTER-HOURS CROWD · © 2026 SUMMER JAM</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,492 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Fieldnote - meetings that end with a plan</title>
|
||||
<meta name="description" content="Fieldnote turns the talk into owners, dates, and next steps before everyone stands up." />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Public+Sans:wght@400;500;600&family=Source+Serif+4:opsz,wght@8..60,400;8..60,600&display=swap" rel="stylesheet" />
|
||||
<style>
|
||||
/* Hallmark · macrostructure: Marquee Hero · theme: Field (candidate) · tone: warm-productivity/friendly */
|
||||
|
||||
:root {
|
||||
/* paper + ink */
|
||||
--paper: oklch(96.5% 0.015 90);
|
||||
--paper-2: oklch(98.5% 0.010 90);
|
||||
--paper-3: oklch(94.2% 0.020 88);
|
||||
--paper-glass: oklch(96.5% 0.015 90 / 0.86);
|
||||
--band-warm: oklch(93% 0.045 84);
|
||||
--ink: oklch(25% 0.02 70);
|
||||
--ink-2: oklch(43% 0.02 72);
|
||||
--ink-3: oklch(55% 0.016 74);
|
||||
--rule: oklch(87% 0.015 90);
|
||||
--rule-2: oklch(83% 0.018 86);
|
||||
|
||||
/* marigold accent family */
|
||||
--accent: oklch(75% 0.13 80);
|
||||
--accent-deep: oklch(58% 0.14 76);
|
||||
--focus: oklch(52% 0.15 76);
|
||||
|
||||
/* shadows (tinted, never neutral-grey) */
|
||||
--shadow-1: 0 1px 2px oklch(30% 0.02 70 / 0.10), 0 10px 24px -16px oklch(30% 0.03 70 / 0.18);
|
||||
--shadow-2: 0 2px 6px oklch(30% 0.02 70 / 0.12), 0 22px 40px -20px oklch(30% 0.03 70 / 0.24);
|
||||
--shadow-note: 0 2px 4px oklch(30% 0.02 70 / 0.08), 0 26px 44px -26px oklch(30% 0.04 70 / 0.30);
|
||||
|
||||
/* type */
|
||||
--font-display: "Source Serif 4", Georgia, serif;
|
||||
--font-body: "Public Sans", system-ui, sans-serif;
|
||||
|
||||
/* radii */
|
||||
--radius: 10px;
|
||||
--radius-lg: 16px;
|
||||
--radius-pill: 999px;
|
||||
|
||||
/* 4pt spacing scale */
|
||||
--space-1: 0.25rem; --space-2: 0.5rem; --space-3: 0.75rem; --space-4: 1rem;
|
||||
--space-5: 1.25rem; --space-6: 1.5rem; --space-8: 2rem; --space-10: 2.5rem;
|
||||
--space-12: 3rem; --space-16: 4rem; --space-20: 5rem;
|
||||
|
||||
--gutter: clamp(20px, 5vw, 64px);
|
||||
--section-y: clamp(64px, 9vw, 112px);
|
||||
--ease: cubic-bezier(0.2, 0.6, 0.3, 1);
|
||||
}
|
||||
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
html, body { overflow-x: clip; }
|
||||
html { scroll-behavior: smooth; }
|
||||
body {
|
||||
background: var(--paper);
|
||||
color: var(--ink-2);
|
||||
font-family: var(--font-body);
|
||||
font-size: 1.0625rem;
|
||||
line-height: 1.6;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
font-family: var(--font-display);
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
color: var(--ink);
|
||||
line-height: 1.08;
|
||||
letter-spacing: -0.01em;
|
||||
overflow-wrap: anywhere;
|
||||
min-width: 0;
|
||||
}
|
||||
p { text-wrap: pretty; }
|
||||
mark { background-color: transparent; color: inherit; }
|
||||
|
||||
.wrap { max-width: 1200px; margin-inline: auto; padding-inline: var(--gutter); }
|
||||
|
||||
.eyebrow {
|
||||
font-family: var(--font-body); font-weight: 600; font-size: 0.8125rem;
|
||||
letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-3);
|
||||
}
|
||||
|
||||
/* ---- the highlighter mark: a marigold band behind the x-height ---- */
|
||||
.mark {
|
||||
padding-inline: 0.06em;
|
||||
background-image: linear-gradient(180deg,
|
||||
transparent 38%, var(--accent) 38%, var(--accent) 92%, transparent 92%);
|
||||
-webkit-box-decoration-break: clone;
|
||||
box-decoration-break: clone;
|
||||
}
|
||||
|
||||
/* ---- hand-drawn ink strokes ---- */
|
||||
.ink { fill: none; stroke: var(--ink); stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
|
||||
.ink-2 { fill: none; stroke: var(--ink-2); stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
|
||||
.acc { fill: none; stroke: var(--accent-deep); stroke-width: 2.6; stroke-linecap: round; stroke-linejoin: round; }
|
||||
.acc-soft { fill: var(--accent); stroke: none; }
|
||||
|
||||
/* ---- buttons ---- */
|
||||
.btn {
|
||||
display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
|
||||
font-family: var(--font-body); font-weight: 600; font-size: 1rem; line-height: 1;
|
||||
padding: var(--space-3) var(--space-6); border-radius: var(--radius);
|
||||
border: 1px solid transparent; cursor: pointer; text-decoration: none; white-space: nowrap;
|
||||
transition: transform 160ms var(--ease), box-shadow 200ms var(--ease), background-color 160ms var(--ease);
|
||||
}
|
||||
.btn--primary { background: var(--ink); color: var(--paper); box-shadow: var(--shadow-1); }
|
||||
.btn--primary:hover { transform: translateY(-2px); box-shadow: var(--shadow-2); }
|
||||
.btn--primary:active { transform: translateY(0); box-shadow: var(--shadow-1); }
|
||||
.btn:focus-visible { outline: 3px solid var(--focus); outline-offset: 3px; }
|
||||
.btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; box-shadow: var(--shadow-1); }
|
||||
|
||||
/* ghost link */
|
||||
.ghost {
|
||||
display: inline-flex; align-items: center; gap: var(--space-2);
|
||||
color: var(--ink); font-weight: 600; text-decoration: underline;
|
||||
text-decoration-color: var(--accent); text-decoration-thickness: 2px; text-underline-offset: 3px;
|
||||
}
|
||||
.ghost svg { transition: transform 180ms var(--ease); }
|
||||
.ghost:hover svg { transform: translateX(4px); }
|
||||
.ghost:focus-visible { outline: 3px solid var(--focus); outline-offset: 3px; border-radius: 4px; }
|
||||
.ghost:active { transform: translateY(1px); }
|
||||
|
||||
/* ---- nav ---- */
|
||||
.nav {
|
||||
position: sticky; top: 0; z-index: 10;
|
||||
background: var(--paper-glass);
|
||||
-webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
|
||||
border-bottom: 1px solid var(--rule);
|
||||
}
|
||||
.nav__inner { display: flex; align-items: center; gap: var(--space-6); padding-block: var(--space-4); }
|
||||
.wordmark {
|
||||
font-family: var(--font-display); font-weight: 600; font-size: 1.375rem; letter-spacing: -0.02em;
|
||||
color: var(--ink); text-decoration: none;
|
||||
}
|
||||
.wordmark:focus-visible { outline: 3px solid var(--focus); outline-offset: 3px; border-radius: 4px; }
|
||||
.nav__links { margin-left: auto; display: flex; align-items: center; gap: var(--space-6); }
|
||||
.nav__link {
|
||||
position: relative; color: var(--ink-2); text-decoration: none; font-weight: 500; font-size: 0.9375rem;
|
||||
transition: color 160ms var(--ease);
|
||||
}
|
||||
.nav__link::after {
|
||||
content: ""; position: absolute; left: 0; right: 0; bottom: -4px; height: 2px;
|
||||
background: var(--accent); transform: scaleX(0); transform-origin: left;
|
||||
transition: transform 180ms var(--ease);
|
||||
}
|
||||
.nav__link:hover { color: var(--ink); }
|
||||
.nav__link:hover::after, .nav__link:focus-visible::after { transform: scaleX(1); }
|
||||
.nav__link:focus-visible { outline: 3px solid var(--focus); outline-offset: 4px; border-radius: 3px; }
|
||||
.pill {
|
||||
border: 1px solid var(--rule-2); border-radius: var(--radius-pill);
|
||||
padding: var(--space-2) var(--space-5); color: var(--ink); text-decoration: none;
|
||||
font-weight: 500; font-size: 0.9375rem; white-space: nowrap;
|
||||
transition: background-color 160ms var(--ease), border-color 160ms var(--ease), transform 160ms var(--ease);
|
||||
}
|
||||
.pill:hover { background: var(--paper-2); border-color: var(--ink-3); }
|
||||
.pill:active { transform: translateY(1px); }
|
||||
.pill:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
|
||||
|
||||
/* ---- hero ---- */
|
||||
.hero__inner {
|
||||
display: grid; grid-template-columns: minmax(0, 1.12fr) minmax(0, 0.88fr);
|
||||
gap: clamp(32px, 5vw, 72px); align-items: center;
|
||||
min-height: clamp(540px, calc(100svh - 76px), 748px);
|
||||
padding-block: clamp(24px, 4vw, 40px) clamp(56px, 9vw, 104px);
|
||||
}
|
||||
.hero__title {
|
||||
font-size: clamp(2.5rem, 1.4rem + 4.6vw, 4rem);
|
||||
letter-spacing: -0.02em; margin-bottom: var(--space-6);
|
||||
}
|
||||
.hero__lede { max-width: 46ch; font-size: 1.1875rem; color: var(--ink-2); margin-bottom: var(--space-8); }
|
||||
.hero__cta { display: flex; align-items: center; gap: var(--space-6); flex-wrap: wrap; }
|
||||
|
||||
.hero__aside { position: relative; display: flex; justify-content: center; }
|
||||
.doodle {
|
||||
position: absolute; left: -76px; bottom: 8px; width: clamp(72px, 9vw, 108px); height: auto;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* the sticky note artifact (a content artifact, not app chrome) */
|
||||
.note {
|
||||
position: relative; width: min(340px, 100%);
|
||||
background: var(--paper-2); border: 1px solid var(--rule-2); border-radius: var(--radius-lg);
|
||||
padding: var(--space-6); box-shadow: var(--shadow-note);
|
||||
transform: rotate(-1.6deg);
|
||||
}
|
||||
.note__top {
|
||||
display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-4);
|
||||
padding-bottom: var(--space-4); margin-bottom: var(--space-4); border-bottom: 1px dashed var(--rule-2);
|
||||
}
|
||||
.note__title { font-family: var(--font-display); font-weight: 600; font-size: 1.0625rem; color: var(--ink); }
|
||||
.note__time { font-size: 0.8125rem; color: var(--ink-3); letter-spacing: 0.02em; }
|
||||
.note__list { list-style: none; display: grid; gap: var(--space-4); }
|
||||
.note__row { display: grid; grid-template-columns: 1.5rem auto 1fr; align-items: center; gap: var(--space-3); }
|
||||
.note__row .tick { width: 1.5rem; height: 1.5rem; }
|
||||
.note__label { font-weight: 600; color: var(--ink); font-size: 0.9375rem; }
|
||||
.note__value { color: var(--ink-3); font-size: 0.9375rem; text-align: right; }
|
||||
|
||||
/* ---- section head (eyebrow stacked ABOVE heading) ---- */
|
||||
.section { padding-block: var(--section-y); }
|
||||
.head { display: flex; flex-direction: column; gap: var(--space-3); max-width: 60ch; margin-bottom: var(--space-12); }
|
||||
.head__title { font-size: clamp(1.75rem, 1.1rem + 2.4vw, 2.5rem); }
|
||||
.head__sub { color: var(--ink-2); font-size: 1.0625rem; }
|
||||
|
||||
/* ---- how it works: three connected beats (flex, staggered) ---- */
|
||||
.beats { position: relative; display: flex; gap: clamp(24px, 4vw, 56px); }
|
||||
.beat { flex: 1 1 0; min-width: 0; position: relative; z-index: 1; }
|
||||
.beat:nth-of-type(2) { margin-top: var(--space-8); }
|
||||
.beat:nth-of-type(3) { margin-top: var(--space-16); }
|
||||
.beat__num { font-family: var(--font-display); font-weight: 600; font-size: 1rem; color: var(--accent-deep); letter-spacing: 0.04em; }
|
||||
.beat__spot { display: block; width: clamp(56px, 7vw, 72px); height: auto; margin: var(--space-4) 0 var(--space-5); }
|
||||
.beat__title { font-size: 1.375rem; margin-bottom: var(--space-3); }
|
||||
.beat__text { color: var(--ink-2); font-size: 1rem; }
|
||||
.beats__thread { position: absolute; inset-inline: 6%; top: 40px; width: 88%; height: 120px; pointer-events: none; z-index: 0; }
|
||||
|
||||
/* ---- features: alternating document rows ---- */
|
||||
.features { display: grid; gap: clamp(48px, 7vw, 88px); }
|
||||
.feature { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: clamp(28px, 5vw, 64px); align-items: center; }
|
||||
.feature__title { font-size: clamp(1.5rem, 1.1rem + 1.4vw, 1.875rem); margin-bottom: var(--space-4); }
|
||||
.feature__text p { color: var(--ink-2); max-width: 54ch; }
|
||||
.feature__tag { display: inline-block; margin-bottom: var(--space-4); }
|
||||
.feature__art { display: flex; justify-content: center; }
|
||||
.feature__art svg { width: min(100%, 300px); height: auto; }
|
||||
.card {
|
||||
background: var(--paper-2); border: 1px solid var(--rule); border-radius: var(--radius-lg);
|
||||
padding: clamp(24px, 4vw, 40px); box-shadow: var(--shadow-1); width: 100%;
|
||||
}
|
||||
.feature--flip .feature__art { order: -1; }
|
||||
|
||||
/* ---- warm CTA band ---- */
|
||||
.band { background: var(--band-warm); border-block: 1px solid var(--rule-2); }
|
||||
.invite__inner { display: grid; gap: var(--space-6); max-width: 60ch; }
|
||||
.invite__title { font-size: clamp(1.875rem, 1.2rem + 2.6vw, 2.75rem); }
|
||||
.invite__lede { color: var(--ink-2); font-size: 1.125rem; max-width: 52ch; }
|
||||
.invite__row { display: flex; align-items: center; gap: var(--space-5); flex-wrap: wrap; margin-top: var(--space-2); }
|
||||
.invite__fine { font-size: 0.9375rem; color: var(--ink-3); }
|
||||
|
||||
/* ---- footer ---- */
|
||||
.footer { border-top: 1px solid var(--rule); padding-block: var(--space-16) var(--space-12); }
|
||||
.footer__inner { display: flex; flex-wrap: wrap; gap: var(--space-8); justify-content: space-between; }
|
||||
.footer__brand { max-width: 40ch; }
|
||||
.footer__word { font-family: var(--font-display); font-weight: 600; font-size: 1.25rem; color: var(--ink); }
|
||||
.footer__note { margin-top: var(--space-3); color: var(--ink-3); font-size: 0.9375rem; }
|
||||
.footer__links { list-style: none; display: flex; flex-wrap: wrap; gap: var(--space-6); align-items: center; }
|
||||
.footer__links a { color: var(--ink-2); text-decoration: none; font-size: 0.9375rem; font-weight: 500; }
|
||||
.footer__links a:hover { color: var(--ink); text-decoration: underline; text-decoration-color: var(--accent); text-underline-offset: 3px; }
|
||||
.footer__links a:focus-visible { outline: 3px solid var(--focus); outline-offset: 3px; border-radius: 3px; }
|
||||
|
||||
/* ---- responsive ---- */
|
||||
@media (max-width: 900px) {
|
||||
.hero__inner { grid-template-columns: minmax(0, 1fr); min-height: 0; gap: var(--space-12); }
|
||||
.hero__aside { justify-content: flex-start; }
|
||||
.doodle { display: none; }
|
||||
.beats { flex-direction: column; gap: var(--space-10); }
|
||||
.beat:nth-of-type(2), .beat:nth-of-type(3) { margin-top: 0; }
|
||||
.beats__thread { display: none; }
|
||||
.feature, .feature--flip { grid-template-columns: minmax(0, 1fr); }
|
||||
.feature--flip .feature__art { order: 0; }
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.nav__inner { gap: var(--space-4); }
|
||||
.nav__link { display: none; }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
* { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header class="nav">
|
||||
<div class="wrap nav__inner">
|
||||
<a class="wordmark" href="#top">Fieldnote</a>
|
||||
<nav class="nav__links" aria-label="Primary">
|
||||
<a class="nav__link" href="#how">Product</a>
|
||||
<a class="nav__link" href="#pricing">Pricing</a>
|
||||
<a class="pill" href="#signin">Sign in</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main id="top">
|
||||
|
||||
<!-- HERO -->
|
||||
<section class="hero">
|
||||
<div class="wrap hero__inner">
|
||||
<div class="hero__lead">
|
||||
<h1 class="hero__title">Meetings that end with <mark class="mark">a plan</mark>.</h1>
|
||||
<p class="hero__lede">Fieldnote listens to the conversation and turns the talk into owners, dates, and next steps, so the plan is written down before everyone stands up.</p>
|
||||
<div class="hero__cta">
|
||||
<button class="btn btn--primary" type="button">Start free</button>
|
||||
<a class="ghost" href="#how">See how it works
|
||||
<svg width="18" height="14" viewBox="0 0 18 14" aria-hidden="true"><path class="ink" d="M2 7 H15" /><path class="ink" d="M10 2 L15 7 L10 12" /></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hero__aside">
|
||||
<!-- loose hand-drawn arrow, marigold-tipped, pointing at the note -->
|
||||
<svg class="doodle" viewBox="0 0 120 132" aria-hidden="true" fill="none">
|
||||
<path class="ink-2" d="M10 122 C 34 128, 58 112, 66 84 C 71 66, 66 50, 52 46 C 41 43, 33 52, 38 62 C 43 72, 60 70, 74 56 C 86 44, 92 32, 92 20" />
|
||||
<path class="acc" d="M78 22 L93 17 L96 33" />
|
||||
</svg>
|
||||
|
||||
<!-- the sticky-note artifact -->
|
||||
<div class="note">
|
||||
<div class="note__top">
|
||||
<span class="note__title">Monday standup</span>
|
||||
<span class="note__time">9:02</span>
|
||||
</div>
|
||||
<ul class="note__list">
|
||||
<li class="note__row">
|
||||
<svg class="tick" viewBox="0 0 24 24" aria-hidden="true"><path class="acc" d="M4 13 l5 5 l11 -13" /></svg>
|
||||
<span class="note__label">Owner</span>
|
||||
<span class="note__value">Priya M.</span>
|
||||
</li>
|
||||
<li class="note__row">
|
||||
<svg class="tick" viewBox="0 0 24 24" aria-hidden="true"><path class="acc" d="M4 13 l5 5 l11 -13" /></svg>
|
||||
<span class="note__label">Due</span>
|
||||
<span class="note__value">Thursday</span>
|
||||
</li>
|
||||
<li class="note__row">
|
||||
<svg class="tick" viewBox="0 0 24 24" aria-hidden="true"><path class="acc" d="M4 13 l5 5 l11 -13" /></svg>
|
||||
<span class="note__label">Decision logged</span>
|
||||
<span class="note__value">Ship the beta</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- HOW IT WORKS -->
|
||||
<section class="section" id="how">
|
||||
<div class="wrap">
|
||||
<div class="head">
|
||||
<p class="eyebrow">How it works</p>
|
||||
<h2 class="head__title">From talk to plan, in three moves.</h2>
|
||||
<p class="head__sub">No bot in the meeting asking people to repeat themselves. Fieldnote stays quiet and does the writing.</p>
|
||||
</div>
|
||||
|
||||
<div class="beats">
|
||||
<svg class="beats__thread" viewBox="0 0 900 120" aria-hidden="true" fill="none" preserveAspectRatio="none">
|
||||
<path class="ink-2" style="stroke-dasharray: 3 9" d="M8 14 C 170 42, 300 24, 460 58 C 620 90, 754 74, 892 106" />
|
||||
</svg>
|
||||
|
||||
<div class="beat">
|
||||
<span class="beat__num">01</span>
|
||||
<svg class="beat__spot" viewBox="0 0 72 72" aria-hidden="true" fill="none">
|
||||
<path class="ink" d="M10 16 h38 a6 6 0 0 1 6 6 v18 a6 6 0 0 1 -6 6 h-22 l-12 10 v-10 h-4 a6 6 0 0 1 -6 -6 v-18 a6 6 0 0 1 6 -6 z" />
|
||||
<path class="acc" d="M18 26 h24 M18 34 h16" />
|
||||
</svg>
|
||||
<h3 class="beat__title">It listens</h3>
|
||||
<p class="beat__text">Add Fieldnote to the call. It follows the conversation and keeps its head down while people talk.</p>
|
||||
</div>
|
||||
|
||||
<div class="beat">
|
||||
<span class="beat__num">02</span>
|
||||
<svg class="beat__spot" viewBox="0 0 72 72" aria-hidden="true" fill="none">
|
||||
<path class="ink" d="M14 12 h26 l18 18 v30 a4 4 0 0 1 -4 4 h-40 a4 4 0 0 1 -4 -4 v-44 a4 4 0 0 1 4 -4 z" />
|
||||
<path class="ink-2" d="M40 12 v18 h18" />
|
||||
<path class="acc" d="M22 44 l6 6 l14 -16" />
|
||||
</svg>
|
||||
<h3 class="beat__title">It sorts</h3>
|
||||
<p class="beat__text">Every commitment becomes an owner, a date, and a decision, written the moment it is said out loud.</p>
|
||||
</div>
|
||||
|
||||
<div class="beat">
|
||||
<span class="beat__num">03</span>
|
||||
<svg class="beat__spot" viewBox="0 0 72 72" aria-hidden="true" fill="none">
|
||||
<path class="ink" d="M10 20 h52 a4 4 0 0 1 4 4 v28 a4 4 0 0 1 -4 4 h-52 a4 4 0 0 1 -4 -4 v-28 a4 4 0 0 1 4 -4 z" />
|
||||
<path class="ink-2" d="M8 23 l28 20 l28 -20" />
|
||||
<path class="acc" d="M52 12 l8 8 M60 12 l-8 8" />
|
||||
</svg>
|
||||
<h3 class="beat__title">It hands off</h3>
|
||||
<p class="beat__text">The plan lands in your inbox and your task tools before the room clears. Nobody re-types the notes.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- FEATURES (alternating document rows) -->
|
||||
<section class="section" id="features">
|
||||
<div class="wrap">
|
||||
<div class="head">
|
||||
<p class="eyebrow">What you get</p>
|
||||
<h2 class="head__title">A record the room can trust.</h2>
|
||||
</div>
|
||||
|
||||
<div class="features">
|
||||
|
||||
<article class="feature">
|
||||
<div class="feature__text">
|
||||
<span class="eyebrow feature__tag">Owners, not action items</span>
|
||||
<h3 class="feature__title">Every next step has a name on it.</h3>
|
||||
<p>A task with no owner is a task nobody does. Fieldnote assigns each next step to the person who spoke up for it, and asks in the room if it is not obvious. You leave with a list of people, not a pile of verbs.</p>
|
||||
</div>
|
||||
<div class="feature__art">
|
||||
<div class="card">
|
||||
<svg viewBox="0 0 300 150" aria-hidden="true" fill="none">
|
||||
<path class="ink" d="M24 40 h150 a10 10 0 0 1 10 10 v52 a10 10 0 0 1 -10 10 h-150 a10 10 0 0 1 -10 -10 v-52 a10 10 0 0 1 10 -10 z" />
|
||||
<circle class="ink-2" cx="52" cy="76" r="16" />
|
||||
<path class="ink-2" d="M92 66 h70 M92 84 h44" />
|
||||
<path class="acc" d="M206 76 l10 10 l22 -26" />
|
||||
<path class="ink-2" style="stroke-dasharray: 2 7" d="M206 108 h64" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="feature feature--flip">
|
||||
<div class="feature__text">
|
||||
<span class="eyebrow feature__tag">Dates it can defend</span>
|
||||
<h3 class="feature__title">Due dates tied to what was actually said.</h3>
|
||||
<p>When someone says "by end of week," Fieldnote writes the date, not the phrase, and links it back to the sentence it came from. If a date was never agreed, it says so plainly instead of inventing one.</p>
|
||||
</div>
|
||||
<div class="feature__art">
|
||||
<div class="card">
|
||||
<svg viewBox="0 0 300 160" aria-hidden="true" fill="none">
|
||||
<path class="ink" d="M40 44 h220 a8 8 0 0 1 8 8 v76 a8 8 0 0 1 -8 8 h-220 a8 8 0 0 1 -8 -8 v-76 a8 8 0 0 1 8 -8 z" />
|
||||
<path class="ink-2" d="M32 70 h236" />
|
||||
<path class="ink" d="M74 34 v18 M150 34 v18 M226 34 v18" />
|
||||
<path class="ink-2" d="M60 92 h20 M104 92 h20 M148 92 h20 M192 92 h20 M236 92 h20 M60 116 h20 M104 116 h20" />
|
||||
<rect class="acc-soft" x="140" y="106" width="30" height="22" rx="5" />
|
||||
<path class="acc" d="M196 116 h20" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="feature">
|
||||
<div class="feature__text">
|
||||
<span class="eyebrow feature__tag">Decisions, kept</span>
|
||||
<h3 class="feature__title">The moment a call was made, saved for later.</h3>
|
||||
<p>Two weeks on, when someone asks why you went with the smaller launch, the answer is one search away. Fieldnote keeps the decision and the reasoning next to it, so the plan holds up when memory does not.</p>
|
||||
</div>
|
||||
<div class="feature__art">
|
||||
<div class="card">
|
||||
<svg viewBox="0 0 300 160" aria-hidden="true" fill="none">
|
||||
<path class="ink" d="M56 26 h150 l36 36 v76 a6 6 0 0 1 -6 6 h-180 a6 6 0 0 1 -6 -6 v-106 a6 6 0 0 1 6 -6 z" />
|
||||
<path class="ink-2" d="M206 26 v36 h36" />
|
||||
<path class="ink-2" d="M80 78 h140 M80 98 h140 M80 118 h96" />
|
||||
<path class="acc" d="M78 62 l8 8 l16 -18" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- WARM CTA -->
|
||||
<section class="section band" id="pricing">
|
||||
<div class="wrap invite__inner">
|
||||
<p class="eyebrow">Try it on one meeting</p>
|
||||
<h2 class="invite__title">Bring your next standup. We will write the plan.</h2>
|
||||
<p class="invite__lede">Fieldnote is new, and still small. Start free, point it at one real meeting, and tell us where it gets things wrong. We are a few people who read every note that comes back.</p>
|
||||
<div class="invite__row">
|
||||
<button class="btn btn--primary" type="button">Start free</button>
|
||||
<span class="invite__fine">No card. One workspace. Leave whenever you like.</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="wrap footer__inner">
|
||||
<div class="footer__brand">
|
||||
<p class="footer__word">Fieldnote</p>
|
||||
<p class="footer__note">Meetings that end with a plan. Made by a small team, still early, and still listening.</p>
|
||||
</div>
|
||||
<ul class="footer__links">
|
||||
<li><a href="#how">Product</a></li>
|
||||
<li><a href="#pricing">Pricing</a></li>
|
||||
<li><a href="#signin">Sign in</a></li>
|
||||
<li><a href="#privacy">Privacy</a></li>
|
||||
<li><a href="#contact">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,469 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Büro Grid · Identity and signage systems</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Archivo:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
|
||||
<style>
|
||||
/* Hallmark · macrostructure: Manifesto · theme: Grid (candidate) · tone: rational/Swiss */
|
||||
|
||||
:root {
|
||||
/* palette - every value oklch, referenced by var() only */
|
||||
--paper: oklch(99.4% 0 0);
|
||||
--ink: oklch(15% 0 0);
|
||||
--red: oklch(55% 0.21 28);
|
||||
--grid-line: oklch(93% 0 0);
|
||||
--hair: oklch(87% 0 0);
|
||||
--muted: oklch(45% 0 0);
|
||||
--tint: oklch(97.2% 0 0);
|
||||
--on-ink: oklch(99.4% 0 0);
|
||||
|
||||
/* type */
|
||||
--font-display: "Archivo", sans-serif;
|
||||
--font-body: "Archivo", sans-serif;
|
||||
|
||||
/* 4pt spacing scale */
|
||||
--space-1: 4px;
|
||||
--space-2: 8px;
|
||||
--space-3: 12px;
|
||||
--space-4: 16px;
|
||||
--space-5: 24px;
|
||||
--space-6: 32px;
|
||||
--space-7: 48px;
|
||||
--space-8: 64px;
|
||||
--space-9: 96px;
|
||||
--space-10: 128px;
|
||||
|
||||
/* layout */
|
||||
--page: 1280px;
|
||||
--gutter: clamp(16px, 4vw, 56px);
|
||||
--measure: 64ch;
|
||||
}
|
||||
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
html { overflow-x: clip; scroll-behavior: smooth; }
|
||||
body {
|
||||
overflow-x: clip;
|
||||
background: var(--paper);
|
||||
color: var(--ink);
|
||||
font-family: var(--font-body);
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
padding-inline: var(--gutter);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
a { color: inherit; text-decoration: none; transition: color 0.18s ease; }
|
||||
:where(a):focus-visible { outline: 2px solid var(--red); outline-offset: 3px; }
|
||||
a:active { opacity: 0.7; }
|
||||
::selection { background: var(--red); color: var(--on-ink); }
|
||||
|
||||
.skip {
|
||||
position: absolute; left: var(--space-4); top: -56px; z-index: 30;
|
||||
background: var(--ink); color: var(--on-ink);
|
||||
padding: var(--space-2) var(--space-4);
|
||||
font-size: 13px; font-weight: 600;
|
||||
}
|
||||
.skip:focus-visible { top: var(--space-4); outline: 2px solid var(--red); outline-offset: 3px; }
|
||||
|
||||
/* ---- structural skeleton: the exposed 12-column hairline grid ---- */
|
||||
.page { position: relative; }
|
||||
.rails { position: absolute; inset: 0; z-index: 0; pointer-events: none; }
|
||||
.rails__inner {
|
||||
height: 100%;
|
||||
max-width: var(--page);
|
||||
margin-inline: auto;
|
||||
background-image: repeating-linear-gradient(
|
||||
to right,
|
||||
var(--grid-line) 0,
|
||||
var(--grid-line) 1px,
|
||||
transparent 1px,
|
||||
transparent calc(100% / 12)
|
||||
);
|
||||
}
|
||||
.shell { position: relative; z-index: 1; max-width: var(--page); margin-inline: auto; }
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(12, minmax(0, 1fr));
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 12px; font-weight: 600; letter-spacing: 0.09em;
|
||||
text-transform: uppercase; line-height: 1.2; color: var(--muted);
|
||||
}
|
||||
.sec-head { padding-block: var(--space-6) var(--space-6); }
|
||||
.sec-head__title {
|
||||
font-family: var(--font-display); font-weight: 800;
|
||||
font-size: clamp(28px, 3.6vw, 46px); letter-spacing: -0.025em;
|
||||
line-height: 1.02; margin-top: var(--space-3);
|
||||
overflow-wrap: anywhere; min-width: 0;
|
||||
}
|
||||
|
||||
/* ---- top nav: a band of cells divided by hairlines ---- */
|
||||
.topbar {
|
||||
position: sticky; top: 0; z-index: 5;
|
||||
background: var(--paper);
|
||||
border-block: 1px solid var(--ink);
|
||||
}
|
||||
.topbar__cell {
|
||||
grid-column: span 2;
|
||||
display: flex; align-items: center;
|
||||
padding: var(--space-4) var(--space-4);
|
||||
border-inline-start: 1px solid var(--hair);
|
||||
font-size: 13px; font-weight: 500; letter-spacing: 0.04em;
|
||||
text-transform: uppercase; line-height: 1.1;
|
||||
min-width: 0;
|
||||
}
|
||||
.topbar__cell:first-child { border-inline-start: 0; }
|
||||
.topbar__mark {
|
||||
grid-column: span 4;
|
||||
font-weight: 800; font-size: 15px; letter-spacing: -0.01em;
|
||||
text-transform: none;
|
||||
}
|
||||
.topbar__link:hover { text-decoration: underline; text-underline-offset: 4px; }
|
||||
.topbar__lang { justify-content: flex-end; gap: var(--space-2); }
|
||||
.topbar__lang a[aria-current="true"] { color: var(--ink); }
|
||||
.topbar__lang a:not([aria-current="true"]) { color: var(--muted); }
|
||||
.topbar__lang a:hover { color: var(--ink); }
|
||||
.topbar__slash { color: var(--hair); }
|
||||
|
||||
/* ---- hero ---- */
|
||||
.hero { padding-block: var(--space-8) clamp(64px, 12vw, 152px); }
|
||||
.hero__title {
|
||||
font-family: var(--font-display); font-weight: 800;
|
||||
font-size: clamp(52px, 10.5vw, 136px);
|
||||
letter-spacing: -0.045em; line-height: 0.9;
|
||||
text-transform: lowercase;
|
||||
margin-block: var(--space-5) var(--space-7);
|
||||
overflow-wrap: anywhere; min-width: 0;
|
||||
}
|
||||
.hero__period {
|
||||
display: inline-block;
|
||||
width: 0.52em; height: 0.52em;
|
||||
margin-left: 0.08em;
|
||||
background: var(--red);
|
||||
vertical-align: baseline;
|
||||
}
|
||||
.hero__foot {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, var(--measure)) minmax(0, 1fr);
|
||||
gap: var(--space-6);
|
||||
align-items: end;
|
||||
border-top: 2px solid var(--ink);
|
||||
padding-top: var(--space-5);
|
||||
}
|
||||
.hero__lead { max-width: var(--measure); font-size: 17px; color: var(--ink); }
|
||||
.hero__note {
|
||||
font-size: 13px; color: var(--muted);
|
||||
text-transform: uppercase; letter-spacing: 0.06em; line-height: 1.5;
|
||||
justify-self: end; text-align: right;
|
||||
}
|
||||
|
||||
/* ---- method band: practice / method / standort ---- */
|
||||
.band { padding-block: clamp(48px, 8vw, 112px); border-top: 1px solid var(--ink); }
|
||||
.band__cols { margin-top: var(--space-4); }
|
||||
.band__col {
|
||||
grid-column: span 4;
|
||||
padding-inline-end: var(--space-6);
|
||||
padding-top: var(--space-4);
|
||||
border-top: 1px solid var(--hair);
|
||||
min-width: 0;
|
||||
}
|
||||
.band__col:first-child { border-top-color: var(--ink); }
|
||||
.band__num {
|
||||
font-family: var(--font-display); font-weight: 800; font-size: 14px;
|
||||
color: var(--muted); letter-spacing: 0;
|
||||
}
|
||||
.band__col h3 {
|
||||
font-family: var(--font-display); font-weight: 800;
|
||||
font-size: 20px; letter-spacing: -0.01em; line-height: 1.1;
|
||||
margin-block: var(--space-3) var(--space-3);
|
||||
overflow-wrap: anywhere; min-width: 0;
|
||||
}
|
||||
.band__col p { font-size: 15px; color: var(--ink); max-width: 46ch; }
|
||||
|
||||
/* ---- work index: numbered full-width rows ---- */
|
||||
.work { padding-block: clamp(48px, 8vw, 112px); border-top: 1px solid var(--ink); }
|
||||
.work__list { margin-top: var(--space-5); border-top: 2px solid var(--ink); list-style: none; }
|
||||
.work__list > li { list-style: none; }
|
||||
.row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(12, minmax(0, 1fr));
|
||||
gap: 0; align-items: baseline;
|
||||
padding-block: var(--space-5);
|
||||
border-bottom: 1px solid var(--hair);
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
.row:hover { background: var(--tint); }
|
||||
.row:active { opacity: 0.82; }
|
||||
.row:focus-visible { outline: 2px solid var(--red); outline-offset: -2px; }
|
||||
.row__no {
|
||||
grid-column: span 1;
|
||||
font-family: var(--font-display); font-weight: 800; font-size: 15px;
|
||||
color: var(--muted); padding-inline-start: var(--space-1);
|
||||
}
|
||||
.row__title {
|
||||
grid-column: span 7;
|
||||
font-family: var(--font-display); font-weight: 700;
|
||||
font-size: clamp(19px, 2.4vw, 27px); letter-spacing: -0.02em;
|
||||
line-height: 1.08; min-width: 0; overflow-wrap: anywhere;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
.row:hover .row__title { transform: translateX(var(--space-2)); }
|
||||
.row__meta {
|
||||
grid-column: span 3;
|
||||
font-size: 13px; color: var(--muted);
|
||||
text-transform: uppercase; letter-spacing: 0.05em; line-height: 1.5;
|
||||
min-width: 0;
|
||||
}
|
||||
.row__mark { grid-column: span 1; justify-self: end; align-self: center; }
|
||||
.row__mark span {
|
||||
display: block; width: 12px; height: 12px; background: var(--red);
|
||||
}
|
||||
|
||||
/* ---- manifesto ---- */
|
||||
.manifesto { padding-block: clamp(64px, 11vw, 152px); border-top: 1px solid var(--ink); }
|
||||
.manifesto__body { margin-top: var(--space-6); }
|
||||
.manifesto__line {
|
||||
grid-column: 1 / span 10;
|
||||
font-family: var(--font-display); font-weight: 500;
|
||||
font-size: clamp(27px, 4.6vw, 54px);
|
||||
letter-spacing: -0.02em; line-height: 1.1;
|
||||
overflow-wrap: anywhere; min-width: 0;
|
||||
}
|
||||
.manifesto__dot { color: var(--red); }
|
||||
|
||||
/* ---- contact ---- */
|
||||
.contact { padding-block: clamp(48px, 8vw, 112px); border-top: 1px solid var(--ink); }
|
||||
.contact__rule { height: 3px; width: var(--space-9); background: var(--red); }
|
||||
.contact .sec-head .label { margin-top: var(--space-4); }
|
||||
.contact__body { margin-top: var(--space-6); }
|
||||
.contact__main { grid-column: 1 / span 7; min-width: 0; }
|
||||
.contact__mail {
|
||||
display: inline-block;
|
||||
font-family: var(--font-display); font-weight: 800;
|
||||
font-size: clamp(24px, 4vw, 44px); letter-spacing: -0.025em;
|
||||
line-height: 1.05; overflow-wrap: anywhere;
|
||||
border-bottom: 2px solid var(--hair); padding-bottom: var(--space-2);
|
||||
transition: border-color 0.18s ease, color 0.18s ease;
|
||||
}
|
||||
.contact__mail:hover { border-bottom-color: var(--red); }
|
||||
.contact__meta { grid-column: 9 / span 4; align-self: end; min-width: 0; }
|
||||
.contact__meta p { font-size: 14px; color: var(--muted); line-height: 1.6; }
|
||||
.contact__meta p + p { margin-top: var(--space-4); }
|
||||
|
||||
/* ---- footer: another band of equal cells ---- */
|
||||
.foot { border-top: 1px solid var(--ink); padding-block: var(--space-7) var(--space-8); }
|
||||
.foot__cell {
|
||||
grid-column: span 3;
|
||||
padding-inline: var(--space-4);
|
||||
border-inline-start: 1px solid var(--hair);
|
||||
font-size: 13px; color: var(--muted); line-height: 1.6;
|
||||
min-width: 0; overflow-wrap: anywhere;
|
||||
}
|
||||
.foot__cell:first-child { border-inline-start: 0; padding-inline-start: 0; }
|
||||
.foot__mark {
|
||||
font-family: var(--font-display); font-weight: 800; font-size: 15px;
|
||||
color: var(--ink); letter-spacing: -0.01em; margin-bottom: var(--space-2);
|
||||
}
|
||||
.foot__cell a:hover { color: var(--ink); text-decoration: underline; text-underline-offset: 3px; }
|
||||
|
||||
/* ---- responsive ---- */
|
||||
@media (max-width: 860px) {
|
||||
.hero__foot { grid-template-columns: 1fr; gap: var(--space-5); }
|
||||
.hero__note { justify-self: start; text-align: left; }
|
||||
.band__col { grid-column: span 6; }
|
||||
.band__col:nth-child(2) { border-top-color: var(--ink); }
|
||||
.contact__main { grid-column: 1 / -1; }
|
||||
.contact__meta { grid-column: 1 / -1; margin-top: var(--space-6); align-self: start; }
|
||||
.manifesto__line { grid-column: 1 / -1; }
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
.topbar__cell { grid-column: span 6; padding-block: var(--space-3); }
|
||||
.topbar__mark { grid-column: 1 / -1; border-bottom: 1px solid var(--hair); }
|
||||
.topbar__cell:nth-child(2), .topbar__cell:nth-child(4) { border-inline-start: 0; }
|
||||
.band__col { grid-column: 1 / -1; }
|
||||
.band__col { border-top-color: var(--hair); }
|
||||
.band__col:first-child { border-top-color: var(--ink); }
|
||||
.row { grid-template-columns: auto minmax(0, 1fr); row-gap: var(--space-2); }
|
||||
.row__no { grid-column: 1; }
|
||||
.row__title { grid-column: 2; }
|
||||
.row__meta { grid-column: 2; }
|
||||
.row__mark { grid-column: 2; justify-self: start; }
|
||||
.foot__cell { grid-column: span 6; margin-top: var(--space-5); }
|
||||
.foot__cell:nth-child(-n+2) { margin-top: 0; }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
html { scroll-behavior: auto; }
|
||||
*, *::before, *::after {
|
||||
transition-duration: 0.01ms !important;
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page" id="top">
|
||||
<div class="rails" aria-hidden="true"><div class="rails__inner"></div></div>
|
||||
|
||||
<a class="skip" href="#main">Skip to content</a>
|
||||
|
||||
<div class="shell">
|
||||
<header class="topbar grid">
|
||||
<a class="topbar__cell topbar__mark" href="#top">Büro Grid</a>
|
||||
<a class="topbar__cell topbar__link" href="#work">Work</a>
|
||||
<a class="topbar__cell topbar__link" href="#method">Method</a>
|
||||
<a class="topbar__cell topbar__link" href="#contact">Contact</a>
|
||||
<div class="topbar__cell topbar__lang">
|
||||
<a href="#top" aria-current="true">DE</a>
|
||||
<span class="topbar__slash" aria-hidden="true">/</span>
|
||||
<a href="#top">EN</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main id="main">
|
||||
<section class="hero" aria-labelledby="hero-title">
|
||||
<p class="label">Identity & signage systems</p>
|
||||
<h1 class="hero__title" id="hero-title">schedule,<br />then ship<span class="hero__period" aria-hidden="true"></span></h1>
|
||||
<div class="hero__foot">
|
||||
<p class="hero__lead">Büro Grid builds identity, wayfinding, and editorial systems for institutions that plan in decades. We draw the grid first, so every decision after it has somewhere to stand.</p>
|
||||
<p class="hero__note">Basel, since 2009<br />Working in DE / EN</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="band" id="method" aria-labelledby="method-title">
|
||||
<div class="sec-head">
|
||||
<p class="label">01 · Practice</p>
|
||||
<h2 class="sec-head__title" id="method-title">Structure before surface</h2>
|
||||
</div>
|
||||
<div class="band__cols grid">
|
||||
<div class="band__col">
|
||||
<p class="band__num">A</p>
|
||||
<h3>Practice</h3>
|
||||
<p>Identity systems, signage programmes, and editorial grids. Each project begins as a set of rules, never as a single picture.</p>
|
||||
</div>
|
||||
<div class="band__col">
|
||||
<p class="band__num">B</p>
|
||||
<h3>Method</h3>
|
||||
<p>Fixed modules, one measure, a single accent. The system has to survive twenty years of new hands, new content, and new signage.</p>
|
||||
</div>
|
||||
<div class="band__col">
|
||||
<p class="band__num">C</p>
|
||||
<h3>Standort</h3>
|
||||
<p>Based in Basel, working across the institutional landscape in German and English. On site whenever the building itself needs us.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="work" id="work" aria-labelledby="work-title">
|
||||
<div class="sec-head">
|
||||
<p class="label">02 · Selected work</p>
|
||||
<h2 class="sec-head__title" id="work-title">Index</h2>
|
||||
</div>
|
||||
<ol class="work__list">
|
||||
<li>
|
||||
<a class="row" href="#contact">
|
||||
<span class="row__no">01</span>
|
||||
<span class="row__title">Kunsthalle Basel</span>
|
||||
<span class="row__meta">Wayfinding system · 2024</span>
|
||||
<span class="row__mark" aria-hidden="true"><span></span></span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="row" href="#contact">
|
||||
<span class="row__no">02</span>
|
||||
<span class="row__title">Rheinhafen Authority</span>
|
||||
<span class="row__meta">Identity & signage · 2023</span>
|
||||
<span class="row__mark" aria-hidden="true"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="row" href="#contact">
|
||||
<span class="row__no">03</span>
|
||||
<span class="row__title">Universität Basel Press</span>
|
||||
<span class="row__meta">Editorial grid · 2023</span>
|
||||
<span class="row__mark" aria-hidden="true"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="row" href="#contact">
|
||||
<span class="row__no">04</span>
|
||||
<span class="row__title">Kantonsbibliothek</span>
|
||||
<span class="row__meta">Identity system · 2022</span>
|
||||
<span class="row__mark" aria-hidden="true"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="row" href="#contact">
|
||||
<span class="row__no">05</span>
|
||||
<span class="row__title">Tramnetz Nord</span>
|
||||
<span class="row__meta">Platform signage · 2021</span>
|
||||
<span class="row__mark" aria-hidden="true"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="row" href="#contact">
|
||||
<span class="row__no">06</span>
|
||||
<span class="row__title">Museum für Gestaltung</span>
|
||||
<span class="row__meta">Type & grid manual · 2020</span>
|
||||
<span class="row__mark" aria-hidden="true"></span>
|
||||
</a>
|
||||
</li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<section class="manifesto" aria-labelledby="pos-title">
|
||||
<div class="sec-head">
|
||||
<p class="label" id="pos-title">Position</p>
|
||||
</div>
|
||||
<div class="manifesto__body grid">
|
||||
<p class="manifesto__line">A grid is not decoration. It is a decision made once, in public, so that every decision after it can be smaller, calmer, and easier to defend<span class="manifesto__dot">.</span></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="contact" id="contact" aria-labelledby="contact-title">
|
||||
<div class="sec-head">
|
||||
<div class="contact__rule" aria-hidden="true"></div>
|
||||
<p class="label">Contact</p>
|
||||
<h2 class="sec-head__title" id="contact-title">Start something that outlasts its brief</h2>
|
||||
</div>
|
||||
<div class="contact__body grid">
|
||||
<div class="contact__main">
|
||||
<a class="contact__mail" href="mailto:studio@burogrid.example">studio@burogrid.example</a>
|
||||
</div>
|
||||
<div class="contact__meta">
|
||||
<p>Steinenvorstadt 12<br />4051 Basel, Switzerland</p>
|
||||
<p>New commissions for 2026 open from September.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="foot grid">
|
||||
<div class="foot__cell">
|
||||
<p class="foot__mark">Büro Grid</p>
|
||||
<p>Identity systems and signage, Basel.</p>
|
||||
</div>
|
||||
<div class="foot__cell">
|
||||
<p><a href="mailto:studio@burogrid.example">studio@burogrid.example</a></p>
|
||||
<p>+41 61 000 00 00</p>
|
||||
</div>
|
||||
<div class="foot__cell">
|
||||
<p>Steinenvorstadt 12<br />4051 Basel</p>
|
||||
</div>
|
||||
<div class="foot__cell">
|
||||
<p>DE / EN</p>
|
||||
<p>© 2026 Büro Grid</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,450 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Meridian · Treasury, reconciled while you sleep</title>
|
||||
<meta name="description" content="Meridian is treasury software that reconciles every bank feed, card network, and ledger entry continuously. Wake up to a closed book." />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet" />
|
||||
<style>
|
||||
/* Hallmark · macrostructure: Stat-Led · theme: Ledger (candidate, alternate) · tone: fintech-terminal/cool */
|
||||
:root {
|
||||
/* palette - dark navy terminal, one teal accent */
|
||||
--paper: oklch(20% 0.025 255);
|
||||
--paper-top: oklch(23% 0.028 258);
|
||||
--surface: oklch(24% 0.026 256);
|
||||
--surface-2: oklch(27.5% 0.026 256);
|
||||
--paper-blur: oklch(21% 0.026 256 / 0.82);
|
||||
--ink: oklch(93% 0.01 250);
|
||||
--ink-2: oklch(84% 0.012 250);
|
||||
--muted: oklch(72% 0.015 250);
|
||||
--rule: oklch(32% 0.02 255);
|
||||
--rule-2: oklch(40% 0.02 255);
|
||||
--accent: oklch(78% 0.12 185);
|
||||
--accent-2: oklch(84% 0.11 188);
|
||||
--accent-ink: oklch(20% 0.03 255);
|
||||
--accent-soft: oklch(78% 0.12 185 / 0.12);
|
||||
--focus: oklch(85% 0.13 195);
|
||||
--shadow: oklch(12% 0.02 258 / 0.5);
|
||||
|
||||
/* type */
|
||||
--font-display: "Hanken Grotesk", sans-serif;
|
||||
--font-body: "Hanken Grotesk", sans-serif;
|
||||
--font-mono: "IBM Plex Mono", ui-monospace, monospace;
|
||||
|
||||
/* space - 4pt scale */
|
||||
--space-1: 4px; --space-2: 8px; --space-3: 12px; --space-4: 16px;
|
||||
--space-5: 20px; --space-6: 24px; --space-7: 32px; --space-8: 40px;
|
||||
--space-9: 48px; --space-10: 64px; --space-11: 80px; --space-12: 96px; --space-13: 128px;
|
||||
|
||||
--radius: 10px; --radius-lg: 14px; --radius-sm: 6px;
|
||||
--wrap: 1180px;
|
||||
}
|
||||
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
html, body { overflow-x: clip; }
|
||||
html { scroll-behavior: smooth; }
|
||||
body {
|
||||
background: linear-gradient(180deg, var(--paper-top) 0%, var(--paper) 46%) no-repeat;
|
||||
background-color: var(--paper);
|
||||
color: var(--ink);
|
||||
font-family: var(--font-body);
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
img, svg { max-width: 100%; display: block; }
|
||||
a { color: inherit; text-decoration: none; }
|
||||
|
||||
.wrap { max-width: var(--wrap); margin-inline: auto; padding-inline: clamp(20px, 4vw, 56px); }
|
||||
.mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
|
||||
.pos { color: var(--accent); }
|
||||
.eyebrow {
|
||||
font-family: var(--font-mono); font-size: 0.75rem; letter-spacing: 0.14em;
|
||||
text-transform: uppercase; color: var(--muted); line-height: 1.3;
|
||||
}
|
||||
.skip-link {
|
||||
position: absolute; left: var(--space-3); top: -60px; z-index: 20;
|
||||
background: var(--surface); color: var(--ink); border: 1px solid var(--rule-2);
|
||||
padding: var(--space-2) var(--space-4); border-radius: var(--radius-sm);
|
||||
}
|
||||
.skip-link:focus { top: var(--space-3); }
|
||||
:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; border-radius: var(--radius-sm); }
|
||||
|
||||
/* ---- buttons ---- */
|
||||
.btn {
|
||||
display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
|
||||
font-family: var(--font-body); font-weight: 600; font-size: 0.9375rem; line-height: 1;
|
||||
padding: var(--space-3) var(--space-5); border-radius: var(--radius-sm);
|
||||
border: 1px solid transparent; white-space: nowrap; cursor: pointer;
|
||||
transition: background-color 0.18s ease, border-color 0.18s ease, color 0.18s ease, transform 0.12s ease;
|
||||
}
|
||||
.btn:active { transform: translateY(1px); }
|
||||
.btn--ghost { background: transparent; color: var(--ink); border-color: var(--rule-2); }
|
||||
.btn--accent { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
|
||||
.btn[aria-disabled="true"] { opacity: 0.55; cursor: not-allowed; }
|
||||
@media (hover: hover) {
|
||||
.btn--ghost:hover { border-color: var(--ink-2); background: var(--surface); }
|
||||
.btn--accent:hover { background: var(--accent-2); border-color: var(--accent-2); }
|
||||
}
|
||||
.tlink {
|
||||
color: var(--ink); font-weight: 500; border-bottom: 1px solid var(--rule-2);
|
||||
padding-bottom: 2px; transition: border-color 0.18s ease, color 0.18s ease;
|
||||
}
|
||||
@media (hover: hover) { .tlink:hover { border-color: var(--ink); } }
|
||||
.tlink:active { color: var(--ink-2); }
|
||||
|
||||
/* ---- nav ---- */
|
||||
.nav {
|
||||
position: sticky; top: 0; z-index: 10;
|
||||
background: var(--paper-blur); color: var(--ink);
|
||||
-webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
|
||||
border-bottom: 1px solid var(--rule);
|
||||
}
|
||||
.nav__in { display: flex; align-items: center; gap: var(--space-6); min-height: 64px; }
|
||||
.wordmark { display: inline-flex; align-items: center; gap: var(--space-3); font-family: var(--font-display); font-weight: 600; font-size: 1.0625rem; letter-spacing: -0.01em; }
|
||||
.wordmark::before { content: ""; width: 12px; height: 12px; border: 2px solid var(--rule-2); border-radius: 3px; }
|
||||
.nav__links { display: flex; align-items: center; gap: var(--space-6); margin-left: var(--space-4); }
|
||||
.nav__link { color: var(--muted); font-weight: 500; font-size: 0.9375rem; transition: color 0.18s ease; }
|
||||
@media (hover: hover) { .nav__link:hover { color: var(--ink); } }
|
||||
.nav__link:active { color: var(--ink-2); }
|
||||
.nav__clock { margin-left: auto; font-family: var(--font-mono); font-size: 0.75rem; color: var(--muted); }
|
||||
@media (max-width: 46rem) { .nav__links, .nav__clock { display: none; } .nav__cta { margin-left: auto; } }
|
||||
|
||||
/* ---- hero ---- */
|
||||
.hero { padding-top: var(--space-7); padding-bottom: var(--space-12); }
|
||||
.hero__grid { display: grid; grid-template-columns: minmax(0, 0.92fr) minmax(0, 1.08fr); gap: clamp(var(--space-8), 5vw, var(--space-11)); align-items: start; }
|
||||
.hero__col { min-width: 0; }
|
||||
.hero h1 {
|
||||
font-family: var(--font-display); font-weight: 600;
|
||||
font-size: clamp(2.1rem, 4.6vw, 3.15rem); line-height: 1.05; letter-spacing: -0.03em;
|
||||
margin-top: var(--space-4); color: var(--ink);
|
||||
overflow-wrap: anywhere; min-width: 0; text-wrap: balance;
|
||||
}
|
||||
.hero__lede { margin-top: var(--space-5); max-width: 54ch; color: var(--muted); font-size: 1.0625rem; }
|
||||
.hero__actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-4); margin-top: var(--space-7); }
|
||||
.hero__trust { margin-top: var(--space-6); font-family: var(--font-mono); font-size: 0.75rem; color: var(--muted); letter-spacing: 0.02em; }
|
||||
@media (max-width: 62rem) { .hero__grid { grid-template-columns: minmax(0, 1fr); } }
|
||||
|
||||
/* ---- account panel (the signature) ---- */
|
||||
.panel {
|
||||
min-width: 0; background: var(--surface); color: var(--ink);
|
||||
border: 1px solid var(--rule); border-radius: var(--radius-lg);
|
||||
box-shadow: 0 24px 60px -32px var(--shadow);
|
||||
overflow: clip;
|
||||
}
|
||||
.panel__head {
|
||||
display: flex; align-items: center; gap: var(--space-3);
|
||||
padding: var(--space-4) var(--space-6); border-bottom: 1px solid var(--rule);
|
||||
font-family: var(--font-mono); font-size: 0.75rem; letter-spacing: 0.04em; color: var(--muted);
|
||||
}
|
||||
.panel__acct { color: var(--ink-2); }
|
||||
.status { margin-left: auto; display: inline-flex; align-items: center; gap: var(--space-2); color: var(--accent); }
|
||||
.dot { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); animation: pulse 2.4s ease-in-out infinite; }
|
||||
.panel__body { padding: var(--space-6); }
|
||||
.balance {
|
||||
font-family: var(--font-mono); font-variant-numeric: tabular-nums;
|
||||
color: var(--accent); font-weight: 500; letter-spacing: -0.02em; line-height: 1;
|
||||
font-size: clamp(2.1rem, 5vw, 3.1rem);
|
||||
}
|
||||
.balance__cur { font-size: 0.56em; vertical-align: 0.14em; margin-right: 0.05em; }
|
||||
.balance__dec { opacity: 0.62; }
|
||||
.balance-meta { margin-top: var(--space-3); font-family: var(--font-mono); font-size: 0.8125rem; color: var(--muted); }
|
||||
.balance-meta .sep { opacity: 0.5; margin-inline: var(--space-1); }
|
||||
|
||||
.spark { margin-top: var(--space-5); }
|
||||
.spark svg { width: 100%; height: 56px; }
|
||||
.spark-line { fill: none; stroke: var(--accent); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
|
||||
#spark-grad stop { stop-color: var(--accent); }
|
||||
.spark-fill { fill: url(#spark-grad); }
|
||||
.spark-cap { display: flex; justify-content: space-between; margin-top: var(--space-2); font-family: var(--font-mono); font-size: 0.6875rem; color: var(--muted); letter-spacing: 0.04em; }
|
||||
|
||||
/* ---- ledger table ---- */
|
||||
.ledger-wrap { margin-top: var(--space-5); border-top: 1px solid var(--rule); overflow-x: auto; }
|
||||
.ledger { width: 100%; min-width: 30rem; border-collapse: collapse; font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size: 0.8125rem; }
|
||||
.ledger caption { text-align: left; padding: var(--space-4) 0 var(--space-2); font-size: 0.6875rem; letter-spacing: 0.12em; text-transform: uppercase; color: var(--muted); }
|
||||
.ledger th { text-align: left; font-weight: 500; color: var(--muted); padding: var(--space-2) var(--space-3); border-bottom: 1px solid var(--rule); letter-spacing: 0.06em; text-transform: uppercase; font-size: 0.6875rem; }
|
||||
.ledger th.num, .ledger td.num { text-align: right; white-space: nowrap; }
|
||||
.ledger td { padding: var(--space-3); border-bottom: 1px solid var(--rule); color: var(--ink-2); vertical-align: baseline; }
|
||||
.ledger tbody tr { transition: background-color 0.15s ease; }
|
||||
.ledger tbody tr:last-child td { border-bottom: 0; }
|
||||
@media (hover: hover) { .ledger tbody tr:hover td { background: var(--surface-2); } }
|
||||
.ledger .desc { color: var(--ink); }
|
||||
.ledger .tag { margin-left: var(--space-2); font-size: 0.625rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); border: 1px solid var(--rule); border-radius: 999px; padding: 2px 8px; }
|
||||
.ledger .neg { color: var(--muted); }
|
||||
|
||||
/* ---- generic section ---- */
|
||||
.section { padding-block: clamp(var(--space-10), 8vw, var(--space-13)); border-top: 1px solid var(--rule); }
|
||||
.section__head { max-width: 62ch; }
|
||||
.section__head h2 {
|
||||
font-family: var(--font-display); font-weight: 600;
|
||||
font-size: clamp(1.7rem, 3.2vw, 2.4rem); line-height: 1.1; letter-spacing: -0.02em;
|
||||
margin-top: var(--space-3); color: var(--ink); overflow-wrap: anywhere; min-width: 0;
|
||||
}
|
||||
.section__head p { margin-top: var(--space-4); max-width: 60ch; color: var(--muted); }
|
||||
|
||||
/* ---- how it works ---- */
|
||||
.beats { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-8); margin-top: var(--space-10); }
|
||||
.beat { min-width: 0; border-top: 1px solid var(--rule-2); padding-top: var(--space-5); }
|
||||
.beat__num { font-family: var(--font-mono); font-size: 0.8125rem; color: var(--muted); letter-spacing: 0.1em; }
|
||||
.beat h3 { font-family: var(--font-display); font-weight: 600; font-size: 1.1875rem; letter-spacing: -0.01em; margin-top: var(--space-3); color: var(--ink); overflow-wrap: anywhere; min-width: 0; }
|
||||
.beat p { margin-top: var(--space-3); color: var(--muted); font-size: 0.9375rem; max-width: 46ch; }
|
||||
@media (max-width: 52rem) { .beats { grid-template-columns: minmax(0, 1fr); gap: var(--space-6); } }
|
||||
|
||||
/* ---- datasheet (features, dense spec) ---- */
|
||||
.datasheet { margin-top: var(--space-9); background: var(--surface); color: var(--ink); border: 1px solid var(--rule); border-radius: var(--radius-lg); padding: clamp(var(--space-5), 3vw, var(--space-8)); }
|
||||
.spec { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); column-gap: clamp(var(--space-8), 5vw, var(--space-12)); }
|
||||
.spec__row { display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-5); padding-block: var(--space-4); border-bottom: 1px solid var(--rule); }
|
||||
.spec__row dt { color: var(--ink); font-weight: 500; font-size: 0.9375rem; min-width: 0; }
|
||||
.spec__row dd { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size: 0.8125rem; color: var(--muted); text-align: right; white-space: nowrap; }
|
||||
@media (max-width: 52rem) { .spec { grid-template-columns: minmax(0, 1fr); } .spec__row dd { white-space: normal; } }
|
||||
|
||||
/* ---- trust ---- */
|
||||
.trust__panel { margin-top: var(--space-9); background: var(--surface); color: var(--ink); border: 1px solid var(--rule); border-radius: var(--radius-lg); overflow: clip; }
|
||||
.trust__row { display: grid; grid-template-columns: minmax(0, 14rem) minmax(0, 1fr); gap: var(--space-5) clamp(var(--space-6), 5vw, var(--space-11)); padding: var(--space-6) clamp(var(--space-5), 3vw, var(--space-8)); border-bottom: 1px solid var(--rule); }
|
||||
.trust__row:last-child { border-bottom: 0; }
|
||||
.trust__label { font-family: var(--font-mono); font-size: 0.75rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); }
|
||||
.trust__row p { color: var(--ink-2); font-size: 0.9375rem; max-width: 62ch; }
|
||||
.trust__row p strong { color: var(--ink); font-weight: 600; }
|
||||
@media (max-width: 46rem) { .trust__row { grid-template-columns: minmax(0, 1fr); gap: var(--space-3); } }
|
||||
.chips { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-7); }
|
||||
.chip { font-family: var(--font-mono); font-size: 0.75rem; letter-spacing: 0.02em; color: var(--ink-2); background: var(--surface); border: 1px solid var(--rule-2); border-radius: 999px; padding: var(--space-2) var(--space-4); }
|
||||
|
||||
/* ---- callout / open account ---- */
|
||||
.callout { background: var(--surface); color: var(--ink); border: 1px solid var(--rule); border-radius: var(--radius-lg); padding: clamp(var(--space-8), 6vw, var(--space-12)); text-align: center; overflow: clip; }
|
||||
.callout h2 { font-family: var(--font-display); font-weight: 600; font-size: clamp(1.8rem, 3.6vw, 2.6rem); line-height: 1.08; letter-spacing: -0.02em; color: var(--ink); overflow-wrap: anywhere; min-width: 0; max-width: 20ch; margin-inline: auto; }
|
||||
.callout p { margin-top: var(--space-4); color: var(--muted); max-width: 52ch; margin-inline: auto; }
|
||||
.callout__actions { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-4); margin-top: var(--space-8); }
|
||||
.callout__note { margin-top: var(--space-6); font-family: var(--font-mono); font-size: 0.75rem; color: var(--muted); }
|
||||
|
||||
/* ---- footer ---- */
|
||||
.footer { border-top: 1px solid var(--rule); padding-block: var(--space-10) var(--space-11); color: var(--muted); }
|
||||
.footer__top { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-6); }
|
||||
.footer__nav { display: flex; flex-wrap: wrap; gap: var(--space-3) var(--space-6); margin-left: auto; }
|
||||
.footer__nav a { color: var(--muted); font-size: 0.875rem; transition: color 0.18s ease; }
|
||||
@media (hover: hover) { .footer__nav a:hover { color: var(--ink); } }
|
||||
.footer__nav a:active { color: var(--ink-2); }
|
||||
.footer__meta { display: flex; flex-wrap: wrap; justify-content: space-between; gap: var(--space-4); margin-top: var(--space-8); font-family: var(--font-mono); font-size: 0.75rem; color: var(--muted); }
|
||||
.footer__meta .ok { color: var(--accent); }
|
||||
|
||||
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
html { scroll-behavior: auto; }
|
||||
*, *::before, *::after { animation-duration: 0.001ms !important; animation-iteration-count: 1 !important; transition-duration: 0.001ms !important; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<a class="skip-link" href="#main">Skip to content</a>
|
||||
|
||||
<header class="nav">
|
||||
<div class="wrap nav__in">
|
||||
<span class="wordmark">Meridian</span>
|
||||
<nav class="nav__links" aria-label="Primary">
|
||||
<a class="nav__link" href="#how">Product</a>
|
||||
<a class="nav__link" href="#pricing">Pricing</a>
|
||||
</nav>
|
||||
<span class="nav__clock" aria-hidden="true">UTC 06:00:12</span>
|
||||
<a class="btn btn--ghost nav__cta" href="#open">Open account</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main id="main">
|
||||
|
||||
<!-- HERO · Stat-Led -->
|
||||
<section class="hero wrap" aria-labelledby="hero-h">
|
||||
<div class="hero__grid">
|
||||
<div class="hero__col">
|
||||
<p class="eyebrow">Meridian · Treasury OS</p>
|
||||
<h1 id="hero-h">Treasury, reconciled while you sleep.</h1>
|
||||
<p class="hero__lede">Meridian pulls every bank feed, card network, and ledger entry into a single balance that stays reconciled around the clock. You wake up to a closed book, not a queue of exceptions.</p>
|
||||
<div class="hero__actions">
|
||||
<a class="btn btn--ghost" href="#open">Open account</a>
|
||||
<a class="tlink" href="#how">See how it reconciles</a>
|
||||
</div>
|
||||
<p class="hero__trust">SOC 2 Type II · dual-control transfers · immutable audit log</p>
|
||||
</div>
|
||||
|
||||
<div class="hero__col hero__col--panel">
|
||||
<div class="panel">
|
||||
<div class="panel__head">
|
||||
<span class="panel__acct">OPERATING · USD · ****4471</span>
|
||||
<span class="status"><span class="dot" aria-hidden="true"></span>LIVE</span>
|
||||
</div>
|
||||
<div class="panel__body">
|
||||
<p class="balance" aria-label="Reconciled balance 4,982,114 dollars and 09 cents">
|
||||
<span class="balance__cur">$</span>4,982,114<span class="balance__dec">.09</span>
|
||||
</p>
|
||||
<p class="balance-meta">Reconciled balance <span class="sep">·</span> as of 06:00 UTC <span class="sep">·</span> <span class="pos">+$18,204.55</span> today</p>
|
||||
|
||||
<div class="spark">
|
||||
<svg viewBox="0 0 640 64" preserveAspectRatio="none" aria-hidden="true" focusable="false">
|
||||
<defs>
|
||||
<linearGradient id="spark-grad" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-opacity="0.24" />
|
||||
<stop offset="1" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path class="spark-fill" d="M0 44 L27 46 L54 40 L81 43 L108 36 L135 39 L162 32 L189 30 L216 34 L243 26 L270 28 L297 22 L324 25 L351 19 L378 21 L405 16 L432 18 L459 13 L486 15 L513 11 L540 12 L567 9 L594 10 L621 7 L640 8 L640 64 L0 64 Z" />
|
||||
<path class="spark-line" d="M0 44 L27 46 L54 40 L81 43 L108 36 L135 39 L162 32 L189 30 L216 34 L243 26 L270 28 L297 22 L324 25 L351 19 L378 21 L405 16 L432 18 L459 13 L486 15 L513 11 L540 12 L567 9 L594 10 L621 7 L640 8" />
|
||||
</svg>
|
||||
<div class="spark-cap"><span>30-DAY RECONCILED BALANCE</span><span>UTC</span></div>
|
||||
</div>
|
||||
|
||||
<div class="ledger-wrap">
|
||||
<table class="ledger">
|
||||
<caption>Cleared transactions · live feed</caption>
|
||||
<thead>
|
||||
<tr><th scope="col">Time</th><th scope="col">Description</th><th scope="col" class="num">Amount</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>06:00:07</td><td class="desc">ACH credit · customer settlement <span class="tag">matched</span></td><td class="num pos">+$182,004.12</td></tr>
|
||||
<tr><td>05:58:22</td><td class="desc">Wire out · monthly payroll <span class="tag">matched</span></td><td class="num neg">-$96,540.00</td></tr>
|
||||
<tr><td>05:41:09</td><td class="desc">Card network settlement <span class="tag">matched</span></td><td class="num neg">-$12,878.45</td></tr>
|
||||
<tr><td>05:12:50</td><td class="desc">Interest · overnight sweep <span class="tag">matched</span></td><td class="num pos">+$1,204.77</td></tr>
|
||||
<tr><td>04:47:31</td><td class="desc">ACH credit · marketplace payout <span class="tag">matched</span></td><td class="num pos">+$50,000.00</td></tr>
|
||||
<tr><td>03:20:16</td><td class="desc">FX settlement · EUR/USD <span class="tag">matched</span></td><td class="num pos">+$8,912.30</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- HOW RECONCILIATION WORKS -->
|
||||
<section class="section" id="how" aria-labelledby="how-h">
|
||||
<div class="wrap">
|
||||
<div class="section__head">
|
||||
<p class="eyebrow">How it works</p>
|
||||
<h2 id="how-h">Three steps, run continuously.</h2>
|
||||
<p>No overnight batch, no month-end scramble. Meridian keeps the loop closed every minute of the day.</p>
|
||||
</div>
|
||||
<div class="beats">
|
||||
<article class="beat">
|
||||
<p class="beat__num">01 / INGEST</p>
|
||||
<h3>Feeds sync every 60 seconds</h3>
|
||||
<p>Bank connections, card networks, and your general ledger stream in over signed APIs. Nothing is keyed by hand; nothing waits for a nightly file.</p>
|
||||
</article>
|
||||
<article class="beat">
|
||||
<p class="beat__num">02 / MATCH</p>
|
||||
<h3>Deterministic rules pair entries</h3>
|
||||
<p>Each transaction is matched to its expected ledger entry by amount, date, and counterparty. Anything ambiguous drops into a review queue instead of a silent guess.</p>
|
||||
</article>
|
||||
<article class="beat">
|
||||
<p class="beat__num">03 / CLOSE</p>
|
||||
<h3>Balances lock at 06:00 UTC</h3>
|
||||
<p>The book closes on schedule with every match carrying an immutable reference. You can audit any figure back to the transaction that moved it.</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- FEATURES · dense datasheet -->
|
||||
<section class="section" id="pricing" aria-labelledby="spec-h">
|
||||
<div class="wrap">
|
||||
<div class="section__head">
|
||||
<p class="eyebrow">Capabilities</p>
|
||||
<h2 id="spec-h">A datasheet, not a pitch.</h2>
|
||||
<p>The specification your controller will actually read. Every line is a default you can change, not a feature you have to buy.</p>
|
||||
</div>
|
||||
<div class="datasheet">
|
||||
<dl class="spec">
|
||||
<div class="spec__row"><dt>Reconciliation cadence</dt><dd>Continuous · 60s sync</dd></div>
|
||||
<div class="spec__row"><dt>Match engine</dt><dd>Deterministic + rule overrides</dd></div>
|
||||
<div class="spec__row"><dt>Entities per workspace</dt><dd>Unlimited books, one console</dd></div>
|
||||
<div class="spec__row"><dt>Close schedule</dt><dd>06:00 UTC lock, configurable</dd></div>
|
||||
<div class="spec__row"><dt>Ledger export</dt><dd>CSV · JSON · GAAP-mapped</dd></div>
|
||||
<div class="spec__row"><dt>Review queue</dt><dd>Exceptions only, per rule</dd></div>
|
||||
<div class="spec__row"><dt>Outbound transfers</dt><dd>Dual-control approval</dd></div>
|
||||
<div class="spec__row"><dt>Audit references</dt><dd>Immutable, per line</dd></div>
|
||||
<div class="spec__row"><dt>Feed connectors</dt><dd>Bank · card · ledger APIs</dd></div>
|
||||
<div class="spec__row"><dt>Access model</dt><dd>SSO / SAML · scoped keys</dd></div>
|
||||
<div class="spec__row"><dt>Data residency</dt><dd>US · EU regions</dd></div>
|
||||
<div class="spec__row"><dt>Retention</dt><dd>7-year append-only log</dd></div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- SECURITY / TRUST -->
|
||||
<section class="section" aria-labelledby="trust-h">
|
||||
<div class="wrap">
|
||||
<div class="section__head">
|
||||
<p class="eyebrow">Trust</p>
|
||||
<h2 id="trust-h">Built for the people who sign off.</h2>
|
||||
<p>Treasury software fails closed. These are the controls that let your auditors, your board, and your bank take Meridian at its word.</p>
|
||||
</div>
|
||||
<div class="trust__panel">
|
||||
<div class="trust__row">
|
||||
<p class="trust__label">Encryption</p>
|
||||
<p><strong>AES-256 at rest, TLS 1.3 in transit.</strong> Keys are managed in a hardware security module and rotated on a fixed schedule. No customer data leaves an encrypted boundary.</p>
|
||||
</div>
|
||||
<div class="trust__row">
|
||||
<p class="trust__label">Movement</p>
|
||||
<p><strong>Dual-control on every outbound transfer.</strong> Initiation and approval are separated by role, and no single credential can move funds on its own.</p>
|
||||
</div>
|
||||
<div class="trust__row">
|
||||
<p class="trust__label">Audit</p>
|
||||
<p><strong>Append-only ledger with per-line references.</strong> Every match, override, and export is written to an immutable log that reconstructs any balance on demand.</p>
|
||||
</div>
|
||||
<div class="trust__row">
|
||||
<p class="trust__label">Access</p>
|
||||
<p><strong>SSO, SAML, and SCIM provisioning.</strong> Scoped API keys, granular roles, and session policies keep least-privilege the default rather than the exception.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chips" aria-label="Compliance and standards">
|
||||
<span class="chip">SOC 2 Type II</span>
|
||||
<span class="chip">AES-256</span>
|
||||
<span class="chip">TLS 1.3</span>
|
||||
<span class="chip">SSO / SAML</span>
|
||||
<span class="chip">SCIM</span>
|
||||
<span class="chip">US / EU residency</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- OPEN ACCOUNT · the one teal CTA -->
|
||||
<section class="section" id="open" aria-labelledby="open-h">
|
||||
<div class="wrap">
|
||||
<div class="callout">
|
||||
<p class="eyebrow">Get started</p>
|
||||
<h2 id="open-h">Open an account and close tonight.</h2>
|
||||
<p>Connect your first feed in an afternoon. By the next lock at 06:00 UTC, your balance is reconciled and your close is done.</p>
|
||||
<div class="callout__actions">
|
||||
<a class="btn btn--accent" href="#open">Open an account</a>
|
||||
<a class="btn btn--ghost" href="#how">Book a walkthrough</a>
|
||||
</div>
|
||||
<p class="callout__note">No card to start · your data stays in your region</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="wrap">
|
||||
<div class="footer__top">
|
||||
<span class="wordmark">Meridian</span>
|
||||
<nav class="footer__nav" aria-label="Footer">
|
||||
<a href="#how">Product</a>
|
||||
<a href="#pricing">Pricing</a>
|
||||
<a href="#how">Docs</a>
|
||||
<a href="#trust-h">Security</a>
|
||||
<a href="#open">Status</a>
|
||||
<a href="#open">Privacy</a>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="footer__meta">
|
||||
<span>Meridian Treasury, Inc. · Figures shown are sample data.</span>
|
||||
<span><span class="ok">●</span> All systems operational · 06:00 UTC</span>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -244,7 +244,7 @@ A custom system is complete (palette + pairing + axes), never a colour swap; its
|
||||
|
||||
Then run the image-need table at [`references/hero-enrichment.md` § Image-need detection](references/hero-enrichment.md). Default is typography-only. Photographic briefs without user assets use the placeholder strategy in [`references/assets.md`](references/assets.md); non-photographic briefs prefer [`references/imagery-kit.md`](references/imagery-kit.md). Never ship invented stock photos as final design.
|
||||
|
||||
**The enrichment hierarchy is non-negotiable:** typography only → pure CSS art → hand-built SVG → generated still → customised library asset → Lottie, last resort. Reaching for Lottie when CSS would build it is the new tell. (Construction guidance lives in `references/craft/`; its files are lettered by construction method - css, svg, animation, webgl, generated, lottie - so match by name.) State the decision in one sentence; it goes into the stamp.
|
||||
**The enrichment hierarchy is non-negotiable:** typography only → pure CSS art → hand-built SVG → generated still → customised library asset → Lottie, last resort. Reaching for Lottie when CSS would build it is the new tell. (Construction guidance lives in `references/craft/`; its files are lettered by construction method - css, svg, animation, webgl, generated, lottie - so match by name.) The **generated-still** rung has an opt-in callable hook, `scripts/imagegen.mjs` (Together images, cache-by-hash), used only when the user asks for real imagery and `TOGETHER_API_KEY` is set; with no key it falls back to CSS-art / placeholder (see [`references/craft/tier-e-generated.md`](references/craft/tier-e-generated.md)). State the decision in one sentence; it goes into the stamp.
|
||||
|
||||
### 5. Preview
|
||||
|
||||
|
||||
@@ -9,6 +9,16 @@ When characters or specific scenes are needed and hand-build is uneconomical (th
|
||||
| **[Midjourney v8](https://www.midjourney.com)** | ~$0.14 / image | Aesthetic beauty, artistic direction, atmospheric stills | PNG |
|
||||
| **[Flux 2](https://blackforestlabs.ai/)** | ~$0.03 / image | Photorealism, skin / fabric / product detail | PNG |
|
||||
|
||||
### The callable hook (opt-in)
|
||||
|
||||
When `TOGETHER_API_KEY` is set and the user has asked for a generated image, you can fetch one instead of recommending a manual round-trip:
|
||||
|
||||
```
|
||||
node <skill-dir>/scripts/imagegen.mjs "<prompt>" --size 1536x1024 [--model ...] [--out <path>]
|
||||
```
|
||||
|
||||
Default model `black-forest-labs/FLUX.1.1-pro` (photoreal, ~$0.04/MP); `--model google/gemini-3-pro-image` for legible in-image text and brand consistency (carries a SynthID watermark). It caches by prompt hash under `.hallmark/img-cache/` and writes a provenance sidecar. **With no key it exits gracefully (code 3) and you fall back to CSS-art or a labelled placeholder** - the recommend-only behaviour below. Never auto-fire it; it serves image-heavy briefs on request, and every generated asset is still post-processed and provenance-stamped per the discipline below.
|
||||
|
||||
### Discipline when generating
|
||||
|
||||
- **Always post-process.** Add grain, asymmetric crop, hand-drawn overlays, colour grading. Raw model output reads as AI 100 % of the time. The post-process is what makes it ours.
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env node
|
||||
// Hallmark image hook. Generates a photographic / illustrative asset for an
|
||||
// image-heavy brief via Together's images endpoint, caches by prompt hash, and
|
||||
// stamps provenance. Zero dependencies (Node 18+ global fetch). OPT-IN: it only
|
||||
// runs when the user asks for a generated image AND TOGETHER_API_KEY is set;
|
||||
// with no key it prints a graceful fallback message and exits non-zero so the
|
||||
// build falls back to CSS-art / placeholder (today's recommend-only behaviour).
|
||||
//
|
||||
// node imagegen.mjs "<prompt>" [--model <id>] [--size 1024x1024] [--out <path>]
|
||||
// [--n 1] [--steps 4] [--force] [--json]
|
||||
//
|
||||
// Default model: black-forest-labs/FLUX.1.1-pro (photoreal, ~$0.04/MP).
|
||||
// Alternatives: --model google/gemini-3-pro-image (legible in-image text, brand
|
||||
// consistency; carries a SynthID watermark), black-forest-labs/FLUX.1-dev.
|
||||
//
|
||||
// Cache: .hallmark/img-cache/<sha256(model|prompt|size|steps)>.png (+ .json
|
||||
// provenance). A cache hit re-uses the file and never calls the API.
|
||||
|
||||
import { createHash } from "node:crypto";
|
||||
import { mkdirSync, existsSync, writeFileSync, readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
||||
const ENDPOINT = "https://api.together.xyz/v1/images/generations";
|
||||
const DEFAULT_MODEL = "black-forest-labs/FLUX.1.1-pro";
|
||||
const CACHE_DIR = join(process.cwd(), ".hallmark", "img-cache");
|
||||
|
||||
function parseArgs(argv) {
|
||||
const a = { _: [] };
|
||||
for (let i = 0; i < argv.length; i++) {
|
||||
const t = argv[i];
|
||||
if (t.startsWith("--")) {
|
||||
const k = t.slice(2);
|
||||
if (k === "force" || k === "json") a[k] = true;
|
||||
else a[k] = argv[++i];
|
||||
} else a._.push(t);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
function out(obj, code = 0) {
|
||||
if (args.json) process.stdout.write(JSON.stringify(obj) + "\n");
|
||||
else if (obj.path) console.log(obj.path);
|
||||
else if (obj.message) console.error(obj.message);
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
const args = parseArgs(process.argv.slice(2));
|
||||
const prompt = args._.join(" ").trim();
|
||||
if (!prompt) out({ error: 'usage: node imagegen.mjs "<prompt>" [--model ...] [--size WxH] [--out path]' }, 2);
|
||||
|
||||
const model = args.model ?? DEFAULT_MODEL;
|
||||
const size = args.size ?? "1024x1024";
|
||||
const [w, h] = size.split("x").map((n) => parseInt(n, 10));
|
||||
if (!w || !h) out({ error: `bad --size ${size} (want WxH, e.g. 1536x1024)` }, 2);
|
||||
const steps = parseInt(args.steps ?? "4", 10);
|
||||
|
||||
const hash = createHash("sha256").update([model, prompt, size, steps].join("|")).digest("hex").slice(0, 16);
|
||||
const cachePath = args.out ?? join(CACHE_DIR, `${hash}.png`);
|
||||
const metaPath = cachePath.replace(/\.png$/, "") + ".json";
|
||||
|
||||
// --- cache hit -------------------------------------------------------------
|
||||
if (!args.force && existsSync(cachePath)) {
|
||||
out({ path: cachePath, cached: true, model, prompt });
|
||||
}
|
||||
|
||||
// --- key check: graceful fallback (this is the opt-in gate) ----------------
|
||||
const key = process.env.TOGETHER_API_KEY;
|
||||
if (!key) {
|
||||
out({
|
||||
error: "no-key",
|
||||
message:
|
||||
"TOGETHER_API_KEY not set. Image generation is opt-in; falling back to CSS-art / a labelled placeholder. " +
|
||||
"Set TOGETHER_API_KEY to generate real imagery, or keep the typography-only / placeholder path.",
|
||||
}, 3);
|
||||
}
|
||||
|
||||
// --- generate --------------------------------------------------------------
|
||||
try {
|
||||
const res = await fetch(ENDPOINT, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json", authorization: `Bearer ${key}` },
|
||||
body: JSON.stringify({ model, prompt, width: w, height: h, steps, n: parseInt(args.n ?? "1", 10), response_format: "b64_json" }),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const detail = (await res.text()).slice(0, 300);
|
||||
out({ error: `together ${res.status}`, message: `image API ${res.status}: ${detail}` }, 1);
|
||||
}
|
||||
const data = await res.json();
|
||||
const b64 = data?.data?.[0]?.b64_json;
|
||||
if (!b64) out({ error: "no-image", message: "image API returned no b64_json payload" }, 1);
|
||||
|
||||
mkdirSync(CACHE_DIR, { recursive: true });
|
||||
if (args.out) mkdirSync(join(args.out, ".."), { recursive: true });
|
||||
writeFileSync(cachePath, Buffer.from(b64, "base64"));
|
||||
const provenance = { model, prompt, size, steps, hash, generatedVia: "together", note: "generated asset; post-process before shipping; verify licensing", at: new Date().toISOString().slice(0, 10) };
|
||||
writeFileSync(metaPath, JSON.stringify(provenance, null, 2) + "\n");
|
||||
out({ path: cachePath, cached: false, model, prompt, provenance });
|
||||
} catch (e) {
|
||||
out({ error: "fetch-failed", message: `image generation failed: ${String(e?.message ?? e)}. Falling back to placeholder.` }, 1);
|
||||
}
|
||||
Reference in New Issue
Block a user