maix
This commit is contained in:
1 parent
c8740fb1d3
commit
c3f4651e71
11 files changed
+3827
-2
No files matched your search
Binary file not shown.
@@ -0,0 +1,158 @@
|
||||
"""Give every paper in src/data/research.json its own Chladni figure and colour field.
|
||||
|
||||
Each paper gets `figure` (parameters for src/scripts/chladni.ts) and `field` (three colour tokens
|
||||
and their positions), seeded from the paper id so a re-run gives the same result and a new paper
|
||||
gets its own. Generation follows the rules learned while hand-tuning the first figures:
|
||||
|
||||
- plate windows stay clear of both axes and of any coordinate where n·t and m·t are both whole
|
||||
(the field is zero along a straight line there), plates are never mirrored (the seams read as
|
||||
straight lines), and the
|
||||
weights avoid a = ±b (which zeroes the diagonals u = ±v);
|
||||
- hover keeps both plate terms in play (a single sin·sin term is a straight grid);
|
||||
- source figures use three to six sources (two sources draw a straight bisector), spread over
|
||||
enough of a wavelength that they interfere (radius × frequency >= 0.6 for five or six sources,
|
||||
0.35 for three or four); packed tighter they collapse into plain concentric rings;
|
||||
- one colour patch sits in the middle of the card, so no field is mostly black;
|
||||
- line density, the share of grid cells a nodal line passes through, stays within the range of
|
||||
the hand-picked figures, so no card is near-empty or a scribble.
|
||||
|
||||
A paper that already has a figure keeps it unless --regenerate is passed, so hand edits survive.
|
||||
Usage: python3 scripts/research-figures.py [--regenerate] [--report]
|
||||
"""
|
||||
import hashlib, json, math, random, sys
|
||||
|
||||
PATH = 'src/data/research.json'
|
||||
ASPECT = 3 # the card canvas is 3:1
|
||||
Q = 61 # grid for the density check (the site renders at 121)
|
||||
|
||||
COLOURS = [
|
||||
'logo-deep', 'logo-mid', 'logo-teal', 'logo-cyan',
|
||||
'field-indigo', 'field-petrol', 'field-sea', 'field-steel', 'field-dusk',
|
||||
'field-cobalt', 'field-ocean', 'field-glacier', 'field-aqua', 'field-jade', 'field-slate', 'field-plum',
|
||||
]
|
||||
# Light tones carry the glow; at most one per field, so every card keeps a dark side.
|
||||
LIGHT = {'logo-cyan', 'field-glacier', 'field-aqua', 'logo-teal'}
|
||||
|
||||
|
||||
def field_fn(f, shape=None):
|
||||
s = {**f, **(shape or {})}
|
||||
if f['mode'] == 'plate':
|
||||
a, b, n, m = s['a'], s['b'], s['n'], s['m']
|
||||
rot = s.get('rotation', 0)
|
||||
c, si = math.cos(rot), math.sin(rot)
|
||||
ox, oy = f['origin']
|
||||
def z(x, y):
|
||||
dx, dy = x - ox, y - oy
|
||||
u, v = ox + dx * c - dy * si, oy + dx * si + dy * c
|
||||
return a * math.sin(math.pi * n * u) * math.sin(math.pi * m * v) + b * math.sin(math.pi * m * u) * math.sin(math.pi * n * v)
|
||||
return z
|
||||
pts = []
|
||||
for i in range(f['sources']):
|
||||
t = i / f['sources'] * 2 * math.pi - math.pi / 2 + s.get('spin', 0)
|
||||
pts.append((f['origin'][0] + s['radius'] * math.cos(t), f['origin'][1] + s['radius'] * math.sin(t)))
|
||||
k = 2 * math.pi * s['frequency']
|
||||
inv = 1 / s['elasticity']
|
||||
return lambda x, y: sum(math.sin(k * (s['phase'] - inv * math.hypot(x - px, y - py))) for px, py in pts)
|
||||
|
||||
|
||||
def density(f, shape=None):
|
||||
z = field_fn(f, shape)
|
||||
h = f['length']; w = h * ASPECT
|
||||
x0, y0 = f['origin'][0] - w / 2, f['origin'][1] - h / 2
|
||||
g = [[z(x0 + c * w / (Q - 1), y0 + r * h / (Q - 1)) > 0 for c in range(Q)] for r in range(Q)]
|
||||
cells = sum(1 for r in range(Q - 1) for c in range(Q - 1) if len({g[r][c], g[r][c + 1], g[r + 1][c], g[r + 1][c + 1]}) > 1)
|
||||
return cells / (Q - 1) ** 2
|
||||
|
||||
|
||||
def straight_lines(n, m, origin, hx, hy):
|
||||
"""True if the window crosses a coordinate where n·t and m·t are both whole: both terms of the
|
||||
plate vanish there for every value of the other coordinate, which draws a straight line."""
|
||||
for centre, half in ((origin[0], hx), (origin[1], hy)):
|
||||
steps = 400
|
||||
for i in range(steps + 1):
|
||||
t = centre - half + 2 * half * i / steps
|
||||
if abs(n * t - round(n * t)) < 0.03 and abs(m * t - round(m * t)) < 0.03:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def rnd(rng, lo, hi, step=0.01):
|
||||
return round(rng.uniform(lo, hi) / step) * step
|
||||
|
||||
|
||||
def make_plate(rng, rotated):
|
||||
while True:
|
||||
n, m = rnd(rng, 1.1, 4.6), rnd(rng, 1.1, 4.6)
|
||||
if abs(n - m) < 0.35: continue
|
||||
length = rnd(rng, 1.0, 2.8)
|
||||
if not (2.4 <= max(n, m) * length <= 8.5): continue
|
||||
a = rnd(rng, 0.8, 1.7) * rng.choice([-1, 1])
|
||||
b = rnd(rng, 0.4, 1.5) * rng.choice([-1, 1])
|
||||
if abs(abs(a) - abs(b)) < 0.25: continue
|
||||
rotation = rnd(rng, 0.2, 1.3) if rotated else 0
|
||||
# Keep the whole (rotated) window off both axes.
|
||||
half_diag = math.hypot(length * ASPECT / 2, length / 2) if rotated else None
|
||||
hx = half_diag if rotated else length * ASPECT / 2
|
||||
hy = half_diag if rotated else length / 2
|
||||
origin = [-round(hx + rnd(rng, 0.3, 2.5), 2), round(hy + rnd(rng, 0.3, 2.5), 2)]
|
||||
f = {'mode': 'plate', 'a': a, 'b': b, 'n': n, 'm': m, 'origin': origin, 'length': length}
|
||||
if rotated: f['rotation'] = rotation
|
||||
if straight_lines(n, m, origin, hx, hy): continue
|
||||
# Hover: flip the dominant weight and bring it down to 40-75 % of the other. Never near zero
|
||||
# (a single sin·sin term draws a grid of straight lines) and never near a = ±b.
|
||||
ha = None
|
||||
for _ in range(20):
|
||||
cand = round(-math.copysign(1, a) * abs(b) * rng.uniform(0.4, 0.75), 2)
|
||||
if abs(abs(cand) - abs(b)) >= 0.2:
|
||||
ha = cand; break
|
||||
if ha is None: continue
|
||||
f['hover'] = {'a': ha}
|
||||
return f
|
||||
|
||||
|
||||
def make_sources(rng):
|
||||
while True:
|
||||
count = rng.choice([3, 3, 4, 4, 5, 6])
|
||||
length = rnd(rng, 1.8, 2.8)
|
||||
f = {
|
||||
'mode': 'sources', 'sources': count,
|
||||
'frequency': rnd(rng, 1.0, 2.5), 'phase': rnd(rng, 0, 0.5), 'elasticity': 1,
|
||||
'radius': rnd(rng, 0.3, min(1.1, length / 2.2)),
|
||||
'origin': [rnd(rng, -1.3, 1.3), rnd(rng, -0.4, 0.4)], 'length': length,
|
||||
'spin': rnd(rng, 0, 2 * math.pi / count),
|
||||
}
|
||||
if f['radius'] * f['frequency'] < (0.6 if count >= 5 else 0.35): continue
|
||||
f['hover'] = {'phase': round(f['phase'] + rnd(rng, 0.35, 0.55), 2), 'spin': round(f['spin'] + rnd(rng, 0.35, 0.65), 2)}
|
||||
return f
|
||||
|
||||
|
||||
def make_field(rng):
|
||||
while True:
|
||||
cols = rng.sample(COLOURS, 3)
|
||||
if sum(c in LIGHT for c in cols) <= 1: break
|
||||
pos = [[rnd(rng, 30, 70, 1), rnd(rng, 30, 70, 1)]] + [[rnd(rng, 5, 95, 1), rnd(rng, 5, 95, 1)] for _ in range(2)]
|
||||
return {'colors': cols, 'positions': pos}
|
||||
|
||||
|
||||
def generate(pid, lo, hi):
|
||||
rng = random.Random(int(hashlib.sha256(pid.encode()).hexdigest(), 16))
|
||||
for _ in range(200):
|
||||
kind = rng.choices(['sources', 'plate', 'rotated'], [0.45, 0.35, 0.2])[0]
|
||||
f = make_sources(rng) if kind == 'sources' else make_plate(rng, kind == 'rotated')
|
||||
d0, d1 = density(f), density(f, f['hover'])
|
||||
if lo <= d0 <= hi and lo * 0.8 <= d1 <= hi * 1.2:
|
||||
return f, make_field(rng), d0
|
||||
raise SystemExit('no acceptable figure for ' + pid)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
data = json.load(open(PATH))
|
||||
# Density band, calibrated on the 18 hand-picked figures (0.24 to 0.51 on this grid), without
|
||||
# the two busiest, which read as scribbles at small card sizes.
|
||||
lo, hi = 0.23, 0.45
|
||||
regen = '--regenerate' in sys.argv
|
||||
for p in data['papers']:
|
||||
if 'figure' in p and not regen: continue
|
||||
p['figure'], p['field'], d = generate(p['id'], lo, hi)
|
||||
if '--report' in sys.argv: print(f"{d:.3f} {p['figure']['mode']:8} {p['id'][:50]}")
|
||||
json.dump(data, open(PATH, 'w'), indent=2, ensure_ascii=False)
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 549 KiB After Width: | Height: | Size: 370 KiB |
@@ -0,0 +1,332 @@
|
||||
---
|
||||
import data from '../../data/research.json';
|
||||
import { t, type Lang } from '../../i18n/ui';
|
||||
import type { ChladniParams } from '../../scripts/chladni';
|
||||
|
||||
interface Props { lang: Lang }
|
||||
const { lang } = Astro.props;
|
||||
const ui = t(lang);
|
||||
const r = ui.team.research;
|
||||
|
||||
/* Papers from both founders' Google Scholar profiles, deduplicated (src/data/research.json).
|
||||
Rendered most cited first, so the page reads right without script; the script below re-sorts,
|
||||
filters and folds the list. Entries marked display: false stay in the data only. */
|
||||
const papers = data.papers.filter((p) => p.display && p.href);
|
||||
const categories = data.categories.filter((c) => papers.some((p) => p.categories.includes(c)));
|
||||
const countIn = (c: string) => papers.filter((p) => p.categories.includes(c)).length;
|
||||
const INITIAL = 9;
|
||||
const labels = { showAll: r.showAll(papers.length), showAllTpl: r.showAll(999).replace('999', '{n}'), showFewer: r.showFewer, count: r.count(111, 999).replace('111', '{shown}').replace('999', '{total}') };
|
||||
|
||||
/* Every paper carries its own figure (Chladni parameters for src/scripts/chladni.ts) and colour
|
||||
field (three tokens and their positions) in research.json, generated per paper by
|
||||
scripts/research-figures.py. They travel with the paper, so sorting and filtering never move them. */
|
||||
const figure = (paper: (typeof papers)[number]) => ({ quality: 121, ...paper.figure }) as ChladniParams;
|
||||
const fieldStyle = (paper: (typeof papers)[number]) =>
|
||||
paper.field.colors.map((c, k) => `--c${k + 1}: var(--${c}); --ax${k + 1}: ${paper.field.positions[k][0]}%; --ay${k + 1}: ${paper.field.positions[k][1]}%;`).join(' ');
|
||||
---
|
||||
<section class="section research" aria-labelledby="research-title">
|
||||
<div class="container">
|
||||
<div class="grid research__head">
|
||||
<h2 id="research-title" class="h2 research__title">
|
||||
{ui.team.research.title.map((line) => <span class="research__line">{line}</span>)}
|
||||
</h2>
|
||||
<p class="research__intro muted">{ui.team.research.intro}</p>
|
||||
</div>
|
||||
|
||||
<div class="research__controls" data-research-controls hidden>
|
||||
<fieldset class="research__group">
|
||||
<legend class="research__legend">{r.sortLabel}</legend>
|
||||
<div class="research__options">
|
||||
{(['cites', 'newest', 'oldest'] as const).map((key, i) => (
|
||||
<label class="research__option">
|
||||
<input type="radio" name="research-sort" value={key} checked={i === 0} />
|
||||
<span>{r.sort[key]}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="research__group">
|
||||
<legend class="research__legend">{r.filterLabel}</legend>
|
||||
<div class="research__options research__options--wrap">
|
||||
<label class="research__option">
|
||||
<input type="radio" name="research-category" value="" checked />
|
||||
<span>{r.all} <span class="research__n">{papers.length}</span></span>
|
||||
</label>
|
||||
{categories.map((c) => (
|
||||
<label class="research__option">
|
||||
<input type="radio" name="research-category" value={c} />
|
||||
<span>{r.categories[c] ?? c} <span class="research__n">{countIn(c)}</span></span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<ul class="research__list" role="list" data-research-list data-initial={INITIAL}>
|
||||
{papers.map((paper, i) => (
|
||||
<li
|
||||
class="research__item"
|
||||
data-cites={paper.cites}
|
||||
data-date={paper.date ?? String(paper.year)}
|
||||
data-order={i}
|
||||
data-categories={paper.categories.join('|')}
|
||||
>
|
||||
<a class="paper" href={paper.href} rel="noopener" lang="en">
|
||||
<div class="paper__field" style={fieldStyle(paper)}>
|
||||
<canvas class="paper__lines" aria-hidden="true" data-chladni={JSON.stringify(figure(paper))}></canvas>
|
||||
<span class="paper__year">{paper.year}</span>
|
||||
</div>
|
||||
<div class="paper__body">
|
||||
<h3 class="paper__title">{paper.title}</h3>
|
||||
<p class="paper__venue small muted">{paper.venue}</p>
|
||||
<div class="paper__meta small">
|
||||
<span class="muted" lang={lang}>{paper.categories.map((c) => r.categories[c] ?? c).join(', ')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<div class="research__foot">
|
||||
<p class="small muted" aria-live="polite" data-research-count></p>
|
||||
<button class="research__more" type="button" data-research-more hidden data-labels={JSON.stringify(labels)}>{labels.showAll}</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
/* Registered so the field positions can transition on hover. */
|
||||
@property --x1 { syntax: '<percentage>'; inherits: false; initial-value: 50%; }
|
||||
@property --y1 { syntax: '<percentage>'; inherits: false; initial-value: 50%; }
|
||||
@property --x2 { syntax: '<percentage>'; inherits: false; initial-value: 50%; }
|
||||
@property --y2 { syntax: '<percentage>'; inherits: false; initial-value: 50%; }
|
||||
@property --x3 { syntax: '<percentage>'; inherits: false; initial-value: 50%; }
|
||||
@property --y3 { syntax: '<percentage>'; inherits: false; initial-value: 50%; }
|
||||
|
||||
.research__line { display: block; }
|
||||
.research__title { grid-column: 1 / -1; }
|
||||
.research__intro { grid-column: 1 / -1; max-width: var(--measure); }
|
||||
@media (min-width: 48rem) {
|
||||
.research__title { grid-column: 1 / span 7; }
|
||||
.research__intro { grid-column: 9 / span 4; align-self: end; }
|
||||
}
|
||||
|
||||
.research__list {
|
||||
margin-top: var(--space-8);
|
||||
display: grid;
|
||||
gap: var(--gap);
|
||||
}
|
||||
@media (min-width: 40rem) { .research__list { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
||||
@media (min-width: 64rem) { .research__list { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
|
||||
|
||||
.paper {
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr;
|
||||
height: 100%;
|
||||
border: var(--hairline) solid var(--line);
|
||||
text-decoration: none;
|
||||
transition: border-color var(--dur-fast) var(--ease);
|
||||
}
|
||||
.paper:hover { border-color: var(--ink-muted); }
|
||||
.paper:hover .paper__title {
|
||||
text-decoration: underline;
|
||||
text-decoration-thickness: var(--hairline);
|
||||
text-underline-offset: 0.14em;
|
||||
}
|
||||
|
||||
.paper__field {
|
||||
--x1: var(--ax1); --y1: var(--ay1);
|
||||
--x2: var(--ax2); --y2: var(--ay2);
|
||||
--x3: var(--ax3); --y3: var(--ay3);
|
||||
position: relative;
|
||||
aspect-ratio: 316 / 170;
|
||||
overflow: hidden;
|
||||
background-color: var(--night);
|
||||
background-image:
|
||||
radial-gradient(80% 110% at var(--x1) var(--y1), var(--c1), transparent 70%),
|
||||
radial-gradient(70% 100% at var(--x2) var(--y2), var(--c2), transparent 68%),
|
||||
radial-gradient(95% 130% at var(--x3) var(--y3), var(--c3), transparent 72%);
|
||||
transition:
|
||||
--x1 var(--dur-slow) var(--ease), --y1 var(--dur-slow) var(--ease),
|
||||
--x2 var(--dur-slow) var(--ease), --y2 var(--dur-slow) var(--ease),
|
||||
--x3 var(--dur-slow) var(--ease), --y3 var(--dur-slow) var(--ease);
|
||||
}
|
||||
/* Hover answers the pointer: the fields drift a little, nothing lifts or glows. */
|
||||
.paper:hover .paper__field {
|
||||
--x1: calc(var(--ax1) + 7%); --y1: calc(var(--ay1) - 6%);
|
||||
--x2: calc(var(--ax2) - 6%); --y2: calc(var(--ay2) + 7%);
|
||||
--x3: calc(var(--ax3) + 5%); --y3: calc(var(--ay3) + 5%);
|
||||
}
|
||||
/* Film grain over the colour. */
|
||||
.paper__field::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.55 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
|
||||
mix-blend-mode: overlay;
|
||||
opacity: 0.6;
|
||||
}
|
||||
/* The Chladni lines: a 3:1 canvas centred on the field and cropped by it, so the figure keeps
|
||||
its proportions at every card width. Stroke colour comes from `color`. */
|
||||
.paper__lines {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
height: 100%;
|
||||
aspect-ratio: 3 / 1;
|
||||
transform: translateX(-50%);
|
||||
color: color-mix(in srgb, var(--paper) 72%, transparent);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.paper__year {
|
||||
position: absolute;
|
||||
top: var(--space-3);
|
||||
left: var(--space-3);
|
||||
z-index: 1;
|
||||
padding: var(--space-1) var(--space-2);
|
||||
background: var(--paper);
|
||||
color: var(--ink);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--step-micro);
|
||||
font-weight: var(--weight-medium);
|
||||
letter-spacing: var(--track-caps);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.paper__body {
|
||||
display: grid;
|
||||
align-content: space-between;
|
||||
gap: var(--space-4);
|
||||
padding: var(--space-4);
|
||||
}
|
||||
.paper__title {
|
||||
font-size: var(--step-body);
|
||||
font-weight: var(--weight-medium);
|
||||
line-height: var(--leading-small);
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
/* Controls: segmented rows in the menu bar's language, hairline cells and mono caps. */
|
||||
.research__controls {
|
||||
margin-top: var(--space-7);
|
||||
display: grid;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
.research__group { border: 0; padding: 0; margin: 0; min-width: 0; display: grid; gap: var(--space-2); }
|
||||
.research__legend { padding: 0; color: var(--ink-muted); font-size: var(--step-small); }
|
||||
/* Each option draws its own frame and overlaps its neighbours by one line, so a wrapped row
|
||||
never leaves a rule running past its last option. */
|
||||
.research__options { display: flex; flex-wrap: wrap; padding: 0 var(--hairline) var(--hairline) 0; }
|
||||
.research__option { position: relative; display: flex; margin: 0 calc(-1 * var(--hairline)) calc(-1 * var(--hairline)) 0; }
|
||||
.research__option input { position: absolute; opacity: 0; inset: 0; margin: 0; cursor: pointer; }
|
||||
.research__option > span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border: var(--hairline) solid var(--line);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--step-micro);
|
||||
font-weight: var(--weight-medium);
|
||||
letter-spacing: var(--track-caps);
|
||||
text-transform: uppercase;
|
||||
line-height: 1;
|
||||
transition: background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
|
||||
}
|
||||
.research__option:hover > span { background: color-mix(in srgb, var(--ink) var(--tint-hover), transparent); }
|
||||
.research__option input:checked + span { background: var(--ink); color: var(--paper); }
|
||||
.research__option input:focus-visible + span { outline: var(--focus-width) solid var(--ink); outline-offset: calc(-1 * var(--focus-width) - 2px); }
|
||||
.research__n { color: inherit; opacity: 0.6; }
|
||||
|
||||
.research__list { margin-top: var(--space-6); scroll-margin-top: calc(var(--header-height) + var(--space-5)); }
|
||||
.research__item[hidden] { display: none; }
|
||||
|
||||
.paper__meta {
|
||||
padding-top: var(--space-3);
|
||||
border-top: var(--hairline) solid var(--line);
|
||||
}
|
||||
|
||||
.research__foot {
|
||||
margin-top: var(--space-6);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: var(--space-4) var(--space-6);
|
||||
}
|
||||
.research__more {
|
||||
padding: var(--space-3) var(--space-5);
|
||||
border: var(--hairline) solid var(--ink);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--step-micro);
|
||||
font-weight: var(--weight-medium);
|
||||
letter-spacing: var(--track-caps);
|
||||
text-transform: uppercase;
|
||||
transition: background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
|
||||
}
|
||||
.research__more:hover { background: var(--ink); color: var(--paper); }
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mountChladni, type ChladniParams } from '../../scripts/chladni';
|
||||
|
||||
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
|
||||
/* Figures draw when their card comes near the viewport: 39 canvases, most never seen. */
|
||||
const lazy = new IntersectionObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
if (!entry.isIntersecting) continue;
|
||||
const canvas = entry.target as HTMLCanvasElement;
|
||||
lazy.unobserve(canvas);
|
||||
const card = canvas.closest<HTMLElement>('.paper');
|
||||
if (card) mountChladni(canvas, card, JSON.parse(canvas.dataset.chladni!) as ChladniParams, reduce);
|
||||
}
|
||||
}, { rootMargin: '300px 0px' });
|
||||
document.querySelectorAll<HTMLCanvasElement>('[data-chladni]').forEach((c) => lazy.observe(c));
|
||||
|
||||
/* Sort, filter, fold. The server renders every card most cited first; this only re-orders and
|
||||
hides list items, so links, figures and focus stay intact. */
|
||||
const list = document.querySelector<HTMLElement>('[data-research-list]');
|
||||
const controls = document.querySelector<HTMLElement>('[data-research-controls]');
|
||||
const more = document.querySelector<HTMLButtonElement>('[data-research-more]');
|
||||
const count = document.querySelector<HTMLElement>('[data-research-count]');
|
||||
if (list && controls && more && count) {
|
||||
const items = [...list.querySelectorAll<HTMLElement>('.research__item')];
|
||||
const initial = Number(list.dataset.initial) || 9;
|
||||
const labels = JSON.parse(more.dataset.labels!) as { showAllTpl: string; showFewer: string; count: string };
|
||||
let expanded = false;
|
||||
controls.hidden = false;
|
||||
|
||||
const value = (name: string) => controls.querySelector<HTMLInputElement>(`input[name="${name}"]:checked`)?.value ?? '';
|
||||
const compare: Record<string, (a: HTMLElement, b: HTMLElement) => number> = {
|
||||
cites: (a, b) => Number(b.dataset.cites) - Number(a.dataset.cites) || Number(a.dataset.order) - Number(b.dataset.order),
|
||||
newest: (a, b) => (b.dataset.date ?? '').localeCompare(a.dataset.date ?? '') || Number(b.dataset.cites) - Number(a.dataset.cites),
|
||||
oldest: (a, b) => (a.dataset.date ?? '').localeCompare(b.dataset.date ?? '') || Number(b.dataset.cites) - Number(a.dataset.cites),
|
||||
};
|
||||
|
||||
const render = () => {
|
||||
const sort = value('research-sort') || 'cites';
|
||||
const category = value('research-category');
|
||||
const matching = items.filter((li) => !category || li.dataset.categories!.split('|').includes(category));
|
||||
matching.sort(compare[sort]);
|
||||
const visible = expanded ? matching : matching.slice(0, initial);
|
||||
for (const li of items) li.hidden = true;
|
||||
for (const li of matching) { list.append(li); }
|
||||
for (const li of visible) li.hidden = false;
|
||||
count.textContent = labels.count.replace('{shown}', String(visible.length)).replace('{total}', String(matching.length));
|
||||
more.hidden = matching.length <= initial;
|
||||
more.textContent = expanded ? labels.showFewer : labels.showAllTpl.replace('{n}', String(matching.length));
|
||||
more.setAttribute('aria-expanded', String(expanded));
|
||||
};
|
||||
|
||||
controls.addEventListener('change', () => { expanded = false; render(); });
|
||||
more.addEventListener('click', () => {
|
||||
expanded = !expanded;
|
||||
render();
|
||||
if (!expanded) list.scrollIntoView({ block: 'start' });
|
||||
});
|
||||
render();
|
||||
}
|
||||
</script>
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,198 @@
|
||||
{
|
||||
"profile": "https://scholar.google.com/citations?user=MRMV2cwAAAAJ&hl=en",
|
||||
"fetchedAt": "2026-10-03",
|
||||
"entries": [
|
||||
{
|
||||
"title": "Open or not open: Are conventional radio access networks more secure and trustworthy than Open-RAN?",
|
||||
"authors": "F Klement, S Katzenbeisser, V Ulitzsch, J Krämer, S Stanczak, Z Utkovski, ...",
|
||||
"venue": "arXiv preprint arXiv:2204.12227, 2022",
|
||||
"cites": 35,
|
||||
"year": 2022,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=MRMV2cwAAAAJ&pagesize=100&citation_for_view=MRMV2cwAAAAJ:d1gkVwhDpl0C"
|
||||
},
|
||||
{
|
||||
"title": "Toward securing the 6G transition: A comprehensive empirical method to analyze threats in O-RAN environments",
|
||||
"authors": "F Klement, W Liu, S Katzenbeisser",
|
||||
"venue": "IEEE Journal on Selected Areas in Communications 42 (2), 420-431, 2023",
|
||||
"cites": 29,
|
||||
"year": 2023,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=MRMV2cwAAAAJ&pagesize=100&citation_for_view=MRMV2cwAAAAJ:W7OEmFMy1HYC"
|
||||
},
|
||||
{
|
||||
"title": "Securing the open ran infrastructure: Exploring vulnerabilities in kubernetes deployments",
|
||||
"authors": "F Klement, A Brighente, M Polese, M Conti, S Katzenbeisser",
|
||||
"venue": "2024 IEEE 10th International Conference on Network Softwarization (NetSoft …, 2024",
|
||||
"cites": 21,
|
||||
"year": 2024,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=MRMV2cwAAAAJ&pagesize=100&citation_for_view=MRMV2cwAAAAJ:ufrVoPGSRksC"
|
||||
},
|
||||
{
|
||||
"title": "Man-in-the-OBD: A modular, protocol agnostic firewall for automotive dongles to enhance privacy and security",
|
||||
"authors": "F Klement, HC Pöhls, S Katzenbeisser",
|
||||
"venue": "International Workshop on Attacks and Defenses for Internet-of-Things, 143-164, 2022",
|
||||
"cites": 13,
|
||||
"year": 2022,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=MRMV2cwAAAAJ&pagesize=100&citation_for_view=MRMV2cwAAAAJ:9yKSN-GCB0IC"
|
||||
},
|
||||
{
|
||||
"title": "Endless subscriptions: open RAN is open to RIC E2 subscription denial of service attacks",
|
||||
"authors": "F Klement, A Brighente, AK Bhattacharjee, S Cecconello, F Kuipers, ...",
|
||||
"venue": "2025 IEEE 10th European Symposium on Security and Privacy (EuroS&P), 755-770, 2025",
|
||||
"cites": 11,
|
||||
"year": 2025,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=MRMV2cwAAAAJ&pagesize=100&citation_for_view=MRMV2cwAAAAJ:Se3iqnhoufwC"
|
||||
},
|
||||
{
|
||||
"title": "Segmentation and Filtering Are Still the Gold Standard for Privacy in IoT—An In-Depth STRIDE and LINDDUN Analysis of Smart Homes",
|
||||
"authors": "HC Pöhls, F Kügler, E Geloczi, F Klement",
|
||||
"venue": "Future Internet 17 (2), 77, 2025",
|
||||
"cites": 9,
|
||||
"year": 2025,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=MRMV2cwAAAAJ&pagesize=100&citation_for_view=MRMV2cwAAAAJ:roLk4NBRz8UC"
|
||||
},
|
||||
{
|
||||
"title": "Testing physical unclonable functions implemented on commercial off-the-shelf NAND flash memories using programming disturbances",
|
||||
"authors": "NA Anagnostopoulos, Y Fan, MU Saleem, N Mexis, E Gelóczi, F Klement, ...",
|
||||
"venue": "2022 IEEE 12th International Conference on Consumer Electronics (ICCE-Berlin …, 2022",
|
||||
"cites": 8,
|
||||
"year": 2022,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=MRMV2cwAAAAJ&pagesize=100&citation_for_view=MRMV2cwAAAAJ:UeHWp8X0CEIC"
|
||||
},
|
||||
{
|
||||
"title": "Change Your Car's Filters: Efficient Concurrent and Multi-Stage Firewall for OBD-II Network Traffic",
|
||||
"authors": "F Klement, HC Pöhls, S Katzenbeisser",
|
||||
"venue": "2022 IEEE 27th International Workshop on Computer Aided Modeling and Design …, 2022",
|
||||
"cites": 7,
|
||||
"year": 2022,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=MRMV2cwAAAAJ&pagesize=100&citation_for_view=MRMV2cwAAAAJ:qjMakFHDy7sC"
|
||||
},
|
||||
{
|
||||
"title": "Secure Data-Binding in FPGA-based Hardware Architectures utilizing PUFs",
|
||||
"authors": "F Frank, M Schmid, F Klement, P Palani, A Weber, EB Kavun, W Xiong, ...",
|
||||
"venue": "Proceedings of the 19th ACM Asia Conference on Computer and Communications …, 2024",
|
||||
"cites": 5,
|
||||
"year": 2024,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=MRMV2cwAAAAJ&pagesize=100&citation_for_view=MRMV2cwAAAAJ:_FxGoFyzp5QC"
|
||||
},
|
||||
{
|
||||
"title": "One standard to rule them all? Assessing the disruptive potential of jamming attacks on Matter networks",
|
||||
"authors": "F Klement, E Vorderwülbeke, S Katzenbeisser",
|
||||
"venue": "2023 IEEE International Workshop on Information Forensics and Security (WIFS …, 2023",
|
||||
"cites": 5,
|
||||
"year": 2023,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=MRMV2cwAAAAJ&pagesize=100&citation_for_view=MRMV2cwAAAAJ:YsMSGLbcyi4C"
|
||||
},
|
||||
{
|
||||
"title": "Ensuring trustworthy automated road vehicles: A software integrity validation approach",
|
||||
"authors": "D Püllen, F Klement, A Vinel, S Katzenbeisser",
|
||||
"venue": "2023 IEEE International Automated Vehicle Validation Conference (IAVVC), 1-8, 2023",
|
||||
"cites": 5,
|
||||
"year": 2023,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=MRMV2cwAAAAJ&pagesize=100&citation_for_view=MRMV2cwAAAAJ:Y0pCki6q_DkC"
|
||||
},
|
||||
{
|
||||
"title": "SoK: delegated Security in the Internet of Things",
|
||||
"authors": "E Geloczi, F Klement, P Struck, S Katzenbeisser",
|
||||
"venue": "Future Internet 17 (5), 202, 2025",
|
||||
"cites": 4,
|
||||
"year": 2025,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=MRMV2cwAAAAJ&pagesize=100&citation_for_view=MRMV2cwAAAAJ:UebtZRa9Y70C"
|
||||
},
|
||||
{
|
||||
"title": "Unveiling the shadows: An approach towards detection, precise localization, and effective isolation of concealed IoT devices in unfamiliar environments",
|
||||
"authors": "E Geloczi, HC Pöhls, F Klement, J Posegga, S Katzenbeisser",
|
||||
"venue": "Proceedings of the 23rd Workshop on Privacy in the Electronic Society, 109-123, 2023",
|
||||
"cites": 4,
|
||||
"year": 2023,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=MRMV2cwAAAAJ&pagesize=100&citation_for_view=MRMV2cwAAAAJ:LkGwnXOMwfcC"
|
||||
},
|
||||
{
|
||||
"title": "Form Without Function: Agent Social Behavior in the Moltbook Network",
|
||||
"authors": "S Zerhoudi, KG Dastidar, F Klement, A Romazanov, A Einwiller, DH Dang, ...",
|
||||
"venue": "arXiv preprint arXiv:2604.13052, 2026",
|
||||
"cites": 3,
|
||||
"year": 2026,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=MRMV2cwAAAAJ&pagesize=100&citation_for_view=MRMV2cwAAAAJ:3fE2CSJIrl8C"
|
||||
},
|
||||
{
|
||||
"title": "Private and secure over-the-air multi-party communication",
|
||||
"authors": "JJ Brune, M Frey, F Klement, I Bjelaković, S Katzenbeisser, S Stańczak",
|
||||
"venue": "2023 57th Asilomar Conference on Signals, Systems, and Computers, 1697-1701, 2023",
|
||||
"cites": 3,
|
||||
"year": 2023,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=MRMV2cwAAAAJ&pagesize=100&citation_for_view=MRMV2cwAAAAJ:WF5omc3nYNoC"
|
||||
},
|
||||
{
|
||||
"title": "ORCA - An Automated Threat Analysis Pipeline for O-RAN Continuous Development",
|
||||
"authors": "F Klement, A Brighente, M Polese, M Conti, S Katzenbeisser",
|
||||
"venue": "arXiv preprint arXiv:2601.13681, 2026",
|
||||
"cites": 2,
|
||||
"year": 2026,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=MRMV2cwAAAAJ&pagesize=100&citation_for_view=MRMV2cwAAAAJ:8k81kl-MbHgC"
|
||||
},
|
||||
{
|
||||
"title": "FlexRIC Subscription DoS Attack",
|
||||
"authors": "F Klement",
|
||||
"venue": "",
|
||||
"cites": 2,
|
||||
"year": 2025,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=MRMV2cwAAAAJ&pagesize=100&citation_for_view=MRMV2cwAAAAJ:5nxA0vEk-isC"
|
||||
},
|
||||
{
|
||||
"title": "Secure Stitch: Unveiling the Fabric of Security Patterns for the Internet of Things",
|
||||
"authors": "E Geloczi, F Klement, E Gründinger, S Katzenbeisser",
|
||||
"venue": "International Workshop on Security and Trust Management, 65-84, 2023",
|
||||
"cites": 2,
|
||||
"year": 2023,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=MRMV2cwAAAAJ&pagesize=100&citation_for_view=MRMV2cwAAAAJ:zYLM7Y9cAGgC"
|
||||
},
|
||||
{
|
||||
"title": "Keep your Enemies closer: On the minimal Distance of Adversaries when using Channel-based Key Extraction in SISO 6G Systems",
|
||||
"authors": "F Klement, S Takebuchi, T Arul, S Katzenbeisser",
|
||||
"venue": "2023 19th International Conference on Wireless and Mobile Computing …, 2023",
|
||||
"cites": 2,
|
||||
"year": 2023,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=MRMV2cwAAAAJ&pagesize=100&citation_for_view=MRMV2cwAAAAJ:IjCSPb-OGe4C"
|
||||
},
|
||||
{
|
||||
"title": "Enhancing Scalability in Smart Device Networks: A Modular Adaptive IoT Framework for Efficient Resource Allocation and System Growth",
|
||||
"authors": "M Waseem, F Klement",
|
||||
"venue": "",
|
||||
"cites": 1,
|
||||
"year": 2024,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=MRMV2cwAAAAJ&pagesize=100&citation_for_view=MRMV2cwAAAAJ:0EnyYjriUFMC"
|
||||
},
|
||||
{
|
||||
"title": "O-RAN Security: From Standardization and Threat Modeling to Practical Security Testing",
|
||||
"authors": "J Moore, AS Abdalla, F Klement, AR Shakya, P Shantharama, V Marojevic",
|
||||
"venue": "IEEE Open Journal of the Communications Society, 2026",
|
||||
"cites": 0,
|
||||
"year": 2026,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=MRMV2cwAAAAJ&pagesize=100&citation_for_view=MRMV2cwAAAAJ:KlAtU1dfN6UC"
|
||||
},
|
||||
{
|
||||
"title": "6G Research and Innovation Cluster (6G-RIC)-Offene und sichere 6G-Technologien: Weltmarktchance für Deutschland",
|
||||
"authors": "F Klement, S Katzenbeisser",
|
||||
"venue": "Hannover: Technische Informationsbibliothek, 2026",
|
||||
"cites": 0,
|
||||
"year": 2026,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=MRMV2cwAAAAJ&pagesize=100&citation_for_view=MRMV2cwAAAAJ:kNdYIx-mwKoC"
|
||||
},
|
||||
{
|
||||
"title": "Strengthening Security Foundations in Next-G Wireless Telecommunication Systems",
|
||||
"authors": "F Klement",
|
||||
"venue": "Universität Passau, 2025",
|
||||
"cites": 0,
|
||||
"year": 2025,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=MRMV2cwAAAAJ&pagesize=100&citation_for_view=MRMV2cwAAAAJ:hqOjcs7Dif8C"
|
||||
},
|
||||
{
|
||||
"title": "1. Felix Klement, Korbinian Spielvogel, Henrich C. Pöhls (UP) 2. Konstantinos Fysarakis, Manolis Chatzimpyrros, Thodoris Galousis, Michalis Smyrlis (STS), 3. Nikolaos …",
|
||||
"authors": "MF ST",
|
||||
"venue": "",
|
||||
"cites": 0,
|
||||
"year": null,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=MRMV2cwAAAAJ&pagesize=100&citation_for_view=MRMV2cwAAAAJ:u-x6o8ySG0sC"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
{
|
||||
"profile": "https://scholar.google.com/citations?user=o9Gu_FMAAAAJ&hl=en",
|
||||
"fetchedAt": "2026-10-03",
|
||||
"entries": [
|
||||
{
|
||||
"title": "A security process for the automotive service-oriented software architecture",
|
||||
"authors": "D Püllen, F Frank, M Christl, W Liu, S Katzenbeisser",
|
||||
"venue": "IEEE Transactions on Vehicular Technology 73 (4), 5036-5053, 2023",
|
||||
"cites": 15,
|
||||
"year": 2023,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=o9Gu_FMAAAAJ&pagesize=100&citation_for_view=o9Gu_FMAAAAJ:LkGwnXOMwfcC"
|
||||
},
|
||||
{
|
||||
"title": "Spatial correlation in weak physical unclonable functions: A comprehensive overview",
|
||||
"authors": "N Mexis, T Arul, NA Anagnostopoulos, F Frank, S Böttger, M Hartmann, ...",
|
||||
"venue": "2023 26th Euromicro Conference on Digital System Design (DSD), 70-78, 2023",
|
||||
"cites": 14,
|
||||
"year": 2023,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=o9Gu_FMAAAAJ&pagesize=100&citation_for_view=o9Gu_FMAAAAJ:roLk4NBRz8UC"
|
||||
},
|
||||
{
|
||||
"title": "Using memristor arrays as physical unclonable functions",
|
||||
"authors": "F Frank, T Arul, NA Anagnostopoulos, S Katzenbeisser",
|
||||
"venue": "European Symposium on Research in Computer Security, 250-271, 2022",
|
||||
"cites": 10,
|
||||
"year": 2022,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=o9Gu_FMAAAAJ&pagesize=100&citation_for_view=o9Gu_FMAAAAJ:IjCSPb-OGe4C"
|
||||
},
|
||||
{
|
||||
"title": "CNT-PUFs: Highly robust physical unclonable functions based on carbon nanotubes",
|
||||
"authors": "S Böttger, F Frank, NA Anagnostopoulos, A Mohamed, M Hartmann, ...",
|
||||
"venue": "2023 IEEE 23rd International Conference on Nanotechnology (NANO), 1-6, 2023",
|
||||
"cites": 9,
|
||||
"year": 2023,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=o9Gu_FMAAAAJ&pagesize=100&citation_for_view=o9Gu_FMAAAAJ:eQOLeE2rZwMC"
|
||||
},
|
||||
{
|
||||
"title": "Testing physical unclonable functions implemented on commercial off-the-shelf NAND flash memories using programming disturbances",
|
||||
"authors": "NA Anagnostopoulos, Y Fan, MU Saleem, N Mexis, E Gelóczi, F Klement, ...",
|
||||
"venue": "2022 IEEE 12th International Conference on Consumer Electronics (ICCE-Berlin …, 2022",
|
||||
"cites": 8,
|
||||
"year": 2022,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=o9Gu_FMAAAAJ&pagesize=100&citation_for_view=o9Gu_FMAAAAJ:Tyk-4Ss8FVUC"
|
||||
},
|
||||
{
|
||||
"title": "Abusing commodity DRAMs in IoT devices to remotely spy on temperature",
|
||||
"authors": "F Frank, W Xiong, NA Anagnostopoulos, A Schaller, T Arul, F Koushanfar, ...",
|
||||
"venue": "IEEE Transactions on Information Forensics and Security 18, 2991-3005, 2023",
|
||||
"cites": 7,
|
||||
"year": 2023,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=o9Gu_FMAAAAJ&pagesize=100&citation_for_view=o9Gu_FMAAAAJ:Y0pCki6q_DkC"
|
||||
},
|
||||
{
|
||||
"title": "Investigation of Commercial Off-The-Shelf ReRAM Modules for Use as Runtime-Accessible TRNG",
|
||||
"authors": "T Arul, N Mexis, AE George, F Frank, NA Anagnostopoulos, ...",
|
||||
"venue": "2024 27th Euromicro Conference on Digital System Design (DSD), 33-42, 2024",
|
||||
"cites": 5,
|
||||
"year": 2024,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=o9Gu_FMAAAAJ&pagesize=100&citation_for_view=o9Gu_FMAAAAJ:3fE2CSJIrl8C"
|
||||
},
|
||||
{
|
||||
"title": "Secure Data-Binding in FPGA-based Hardware Architectures utilizing PUFs",
|
||||
"authors": "F Frank, M Schmid, F Klement, P Palani, A Weber, EB Kavun, W Xiong, ...",
|
||||
"venue": "Proceedings of the 19th ACM Asia Conference on Computer and Communications …, 2024",
|
||||
"cites": 5,
|
||||
"year": 2024,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=o9Gu_FMAAAAJ&pagesize=100&citation_for_view=o9Gu_FMAAAAJ:UebtZRa9Y70C"
|
||||
},
|
||||
{
|
||||
"title": "CNT-PUFs: highly robust and heat-tolerant carbon-nanotube-based physical unclonable functions",
|
||||
"authors": "F Frank, S Böttger, N Mexis, NA Anagnostopoulos, A Mohamed, ...",
|
||||
"venue": "Nanomaterials 13 (22), 2930, 2023",
|
||||
"cites": 4,
|
||||
"year": 2023,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=o9Gu_FMAAAAJ&pagesize=100&citation_for_view=o9Gu_FMAAAAJ:YsMSGLbcyi4C"
|
||||
},
|
||||
{
|
||||
"title": "Secure communication via chaotic cryptography",
|
||||
"authors": "E Gelóczi, N Mexis, NA Anagnostopoulos, F Frank, T Arul, SG Stavrinides, ...",
|
||||
"venue": "crypto day matters 34, 2022",
|
||||
"cites": 3,
|
||||
"year": 2022,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=o9Gu_FMAAAAJ&pagesize=100&citation_for_view=o9Gu_FMAAAAJ:9yKSN-GCB0IC"
|
||||
},
|
||||
{
|
||||
"title": "Fuzzy extractors using low-density parity-check codes",
|
||||
"authors": "N Mexis, NA Anagnostopoulos, T Arul, F Frank, S Katzenbeisser",
|
||||
"venue": "crypto day matters 33, 2021",
|
||||
"cites": 3,
|
||||
"year": 2021,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=o9Gu_FMAAAAJ&pagesize=100&citation_for_view=o9Gu_FMAAAAJ:d1gkVwhDpl0C"
|
||||
},
|
||||
{
|
||||
"title": "Real-world chaos-based cryptography using synchronised Chua chaotic circuits",
|
||||
"authors": "E Nazarenko, NA Anagnostopoulos, SG Stavrinides, N Mexis, F Frank, ...",
|
||||
"venue": "TechRxiv 2023 (0717), 2023",
|
||||
"cites": 2,
|
||||
"year": 2023,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=o9Gu_FMAAAAJ&pagesize=100&citation_for_view=o9Gu_FMAAAAJ:UeHWp8X0CEIC"
|
||||
},
|
||||
{
|
||||
"title": "A reconfigurable hardware infrastructure for evaluating PUFs in non-volatile memories",
|
||||
"authors": "F Frank, S Katzenbeisser",
|
||||
"venue": "HardwareX, e00809, 2026",
|
||||
"cites": 1,
|
||||
"year": 2026,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=o9Gu_FMAAAAJ&pagesize=100&citation_for_view=o9Gu_FMAAAAJ:KlAtU1dfN6UC"
|
||||
},
|
||||
{
|
||||
"title": "Towards Deterministic DDS Communication for Secure Service-Oriented Software-Defined Vehicles",
|
||||
"authors": "F Frank, D Püllen, A Kampmann, S Katzenbeisser",
|
||||
"venue": "International Conference on Availability, Reliability and Security, 186-208, 2025",
|
||||
"cites": 1,
|
||||
"year": 2025,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=o9Gu_FMAAAAJ&pagesize=100&citation_for_view=o9Gu_FMAAAAJ:kNdYIx-mwKoC"
|
||||
},
|
||||
{
|
||||
"title": "A Dedicated Mixed-Signal Characterisation and Testing Framework for Novel Digital Security Circuits That Use Carbon-Nanotube-Based Physical Unclonable Functions",
|
||||
"authors": "F Frank, NA Anagnostopoulos, S Böttger, S Hermann, T Arul, ...",
|
||||
"venue": "International Conference on Modern Circuits and Systems Technologies (MOCAST) 11, 2022",
|
||||
"cites": 1,
|
||||
"year": 2022,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=o9Gu_FMAAAAJ&pagesize=100&citation_for_view=o9Gu_FMAAAAJ:2osOgNQ5qMEC"
|
||||
},
|
||||
{
|
||||
"title": "On the sustainability of lightweight cryptography based on PUFs implemented on NAND Flash memories using programming disturbances",
|
||||
"authors": "NA Anagnostopoulos, Y Fan, MU Saleem, N Mexis, F Frank, T Arul, ...",
|
||||
"venue": "arXiv preprint arXiv:2204.02498, 2022",
|
||||
"cites": 1,
|
||||
"year": 2022,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=o9Gu_FMAAAAJ&pagesize=100&citation_for_view=o9Gu_FMAAAAJ:u5HHmVD_uO8C"
|
||||
},
|
||||
{
|
||||
"title": "Integrating physical unclonable functions from novel nanomaterials, circuit elements, and memory technologies into future hardware architectures",
|
||||
"authors": "F Frank",
|
||||
"venue": "Universität Passau, 2026",
|
||||
"cites": 0,
|
||||
"year": 2026,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=o9Gu_FMAAAAJ&pagesize=100&citation_for_view=o9Gu_FMAAAAJ:ULOm3_A8WrAC"
|
||||
},
|
||||
{
|
||||
"title": "Securing Next Generation Service-Oriented Vehicle Architectures",
|
||||
"authors": "F Frank, D Püllen, C Krauss, D Spychalski",
|
||||
"venue": "autotech. agil, 2024",
|
||||
"cites": 0,
|
||||
"year": 2024,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=o9Gu_FMAAAAJ&pagesize=100&citation_for_view=o9Gu_FMAAAAJ:4TOpqqG69KYC"
|
||||
},
|
||||
{
|
||||
"title": "Threat Analysis of the autotech. agil Cooperative Intelligent Transport System Ecosystem",
|
||||
"authors": "F Frank, D Püllen, C Krauss, D Spychalski",
|
||||
"venue": "autotech. agil, 2024",
|
||||
"cites": 0,
|
||||
"year": 2024,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=o9Gu_FMAAAAJ&pagesize=100&citation_for_view=o9Gu_FMAAAAJ:_kc_bZDykSQC"
|
||||
},
|
||||
{
|
||||
"title": "Safety & Security Co-Engineering in Autonomous Driving and Future Mobility Systems",
|
||||
"authors": "N Braun, F Frank, C Krauss, D Spychalski, R Schubert, D Püllen",
|
||||
"venue": "autotech. agil, 2024",
|
||||
"cites": 0,
|
||||
"year": 2024,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=o9Gu_FMAAAAJ&pagesize=100&citation_for_view=o9Gu_FMAAAAJ:YOwf2qJgpHMC"
|
||||
},
|
||||
{
|
||||
"title": "CNT-PUFs: Highly Robust and Heat-Tolerant Carbon-Nanotube-Based Physical Unclonable Functions for Stable Key Generation",
|
||||
"authors": "S Böttger, F Frank, NA Anagnostopoulos, N Mexis, A Mohamed, ...",
|
||||
"venue": "TechRxiv 2023 (0717), 2023",
|
||||
"cites": 0,
|
||||
"year": 2023,
|
||||
"scholarUrl": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=o9Gu_FMAAAAJ&pagesize=100&citation_for_view=o9Gu_FMAAAAJ:W7OEmFMy1HYC"
|
||||
}
|
||||
]
|
||||
}
|
||||
+42
-2
@@ -218,7 +218,7 @@ const en = {
|
||||
},
|
||||
team: {
|
||||
title: ['Researchers,', 'engineers,', 'architects.'],
|
||||
intro: 'The people who go deep on the problems that matter, from network software and private 5G to hardware and cloud-native systems.',
|
||||
intro: 'The people who go deep on the problems that matter, from network, software to hardware and cloud-native systems. If it\'s a genuinely hard problem, we\'re in our element.',
|
||||
photoAlt: 'The CipherCell team on a staircase in the offices in Passau.',
|
||||
roles: {
|
||||
ceo: 'Founder and CEO',
|
||||
@@ -228,6 +228,26 @@ const en = {
|
||||
studentF: 'Working student, development',
|
||||
studentM: 'Working student, development',
|
||||
},
|
||||
research: {
|
||||
title: ['Published', 'research.'],
|
||||
intro: 'Papers by our team on Open RAN, cloud-native infrastructure and hardware security. The method we sell grew out of this work.',
|
||||
sortLabel: 'Sort',
|
||||
sort: { cites: 'Most cited', newest: 'Newest', oldest: 'Oldest' },
|
||||
filterLabel: 'Category',
|
||||
all: 'All',
|
||||
showAll: (n: number) => `Show all ${n} papers`,
|
||||
showFewer: 'Show fewer',
|
||||
count: (shown: number, total: number) => `Showing ${shown} of ${total} papers`,
|
||||
categories: {
|
||||
'Open RAN & 6G': 'Open RAN & 6G',
|
||||
'Hardware security': 'Hardware security',
|
||||
'Automotive & mobility': 'Automotive & mobility',
|
||||
'IoT security & privacy': 'IoT security & privacy',
|
||||
'Wireless security': 'Wireless security',
|
||||
'Cryptography': 'Cryptography',
|
||||
'AI agents': 'AI agents',
|
||||
} as Record<string, string>,
|
||||
},
|
||||
},
|
||||
notFound: {
|
||||
title: 'Page not found',
|
||||
@@ -434,7 +454,7 @@ const de: typeof en = {
|
||||
},
|
||||
team: {
|
||||
title: ['Forschende,', 'Ingenieure,', 'Architekten.'],
|
||||
intro: 'Die Menschen, die sich in die Probleme vertiefen, auf die es ankommt: von Netzsoftware und privaten 5G-Netzen bis zu Hardware und Cloud-nativen Systemen.',
|
||||
intro: 'Die Menschen, die sich in die Probleme vertiefen, auf die es ankommt: von Netzwerken über Software bis hin zu Hardware und Cloud-nativen Systemen. Wenn es sich um ein wirklich schwieriges Problem handelt, sind wir in unserem Element.',
|
||||
photoAlt: 'Das CipherCell-Team auf einer Treppe in den Büros in Passau.',
|
||||
roles: {
|
||||
ceo: 'Gründer und CEO',
|
||||
@@ -444,6 +464,26 @@ const de: typeof en = {
|
||||
studentF: 'Werkstudentin, Entwicklung',
|
||||
studentM: 'Werkstudent, Entwicklung',
|
||||
},
|
||||
research: {
|
||||
title: ['Veröffentlichte', 'Forschung.'],
|
||||
intro: 'Arbeiten unseres Teams zu Open RAN, cloud-nativer Infrastruktur und Hardware-Sicherheit. Die Methode, die wir anbieten, ist aus dieser Arbeit entstanden.',
|
||||
sortLabel: 'Sortieren',
|
||||
sort: { cites: 'Meistzitiert', newest: 'Neueste', oldest: 'Älteste' },
|
||||
filterLabel: 'Kategorie',
|
||||
all: 'Alle',
|
||||
showAll: (n: number) => `Alle ${n} Arbeiten zeigen`,
|
||||
showFewer: 'Weniger zeigen',
|
||||
count: (shown: number, total: number) => `${shown} von ${total} Arbeiten`,
|
||||
categories: {
|
||||
'Open RAN & 6G': 'Open RAN & 6G',
|
||||
'Hardware security': 'Hardware-Sicherheit',
|
||||
'Automotive & mobility': 'Automotive & Mobilität',
|
||||
'IoT security & privacy': 'IoT-Sicherheit & Datenschutz',
|
||||
'Wireless security': 'Funksicherheit',
|
||||
'Cryptography': 'Kryptografie',
|
||||
'AI agents': 'KI-Agenten',
|
||||
} as Record<string, string>,
|
||||
},
|
||||
},
|
||||
notFound: {
|
||||
title: 'Seite nicht gefunden',
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Picture } from 'astro:assets';
|
||||
import Base from '../layouts/Base.astro';
|
||||
import teamPhoto from '../assets/images/team.png';
|
||||
import { team } from '../data/team';
|
||||
import Research from '../components/team/Research.astro';
|
||||
import { t, type Lang } from '../i18n/ui';
|
||||
|
||||
interface Props { lang: Lang }
|
||||
@@ -58,6 +59,8 @@ const ui = t(lang);
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Research lang={lang} />
|
||||
</Base>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
/* Chladni figures for the research cards: the nodal lines of a vibrating plate, traced as the zero
|
||||
contour of a wave field sampled on a grid (marching squares). Two fields:
|
||||
|
||||
plate z = a·sin(πn·x)·sin(πm·y) + b·sin(πm·x)·sin(πn·y), optionally rotated, which gives
|
||||
rounded lattices, lenses and, turned, diamond and diagonal weaves;
|
||||
sources waves from `sources` points on a circle around the centre, interfering:
|
||||
z = Σ sin(2π·frequency·(phase − d / elasticity)), which gives rings, trefoils, petals.
|
||||
|
||||
Hovering a card tweens the numbers to its hover set, so the figure reflows into a related one
|
||||
rather than sliding. Cheap by construction: a card draws once on load and on resize, and only
|
||||
redraws during the 600 ms tween after a hover change. Nothing runs while the pointer is still. */
|
||||
|
||||
interface Common {
|
||||
/** Centre of the sampled window. */
|
||||
origin: [number, number];
|
||||
/** Height of the sampled window; its width follows the canvas aspect. */
|
||||
length: number;
|
||||
/** Grid points per side. */
|
||||
quality: number;
|
||||
/** Mirror the lower-right quadrant into all four, around the canvas centre. */
|
||||
quadrant?: boolean;
|
||||
}
|
||||
|
||||
export interface PlateShape { a: number; b: number; n: number; m: number; rotation?: number }
|
||||
export interface SourcesShape { frequency: number; phase: number; elasticity: number; radius: number; spin?: number }
|
||||
|
||||
export type ChladniParams =
|
||||
| (Common & { mode: 'plate'; hover: Partial<PlateShape> } & PlateShape)
|
||||
| (Common & { mode: 'sources'; sources: number; hover: Partial<SourcesShape> } & SourcesShape);
|
||||
|
||||
type Point = [number, number];
|
||||
type Shape = Record<string, number>;
|
||||
|
||||
const DURATION = 600;
|
||||
const PLATE_KEYS = ['a', 'b', 'n', 'm', 'rotation'] as const;
|
||||
const SOURCE_KEYS = ['frequency', 'phase', 'elasticity', 'radius', 'spin'] as const;
|
||||
|
||||
/* cubic-bezier(0.2, 0, 0.3, 1): solved for x by Newton's method, then evaluated for y. */
|
||||
function ease(t: number): number {
|
||||
const x1 = 0.2, y1 = 0, x2 = 0.3, y2 = 1;
|
||||
const bez = (u: number, p1: number, p2: number) => 3 * (1 - u) ** 2 * u * p1 + 3 * (1 - u) * u ** 2 * p2 + u ** 3;
|
||||
const dbez = (u: number, p1: number, p2: number) => 3 * (1 - u) ** 2 * p1 + 6 * (1 - u) * u * (p2 - p1) + 3 * u ** 2 * (1 - p2);
|
||||
let u = t;
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const d = dbez(u, x1, x2);
|
||||
if (Math.abs(d) < 1e-6) break;
|
||||
u -= (bez(u, x1, x2) - t) / d;
|
||||
}
|
||||
return bez(Math.min(Math.max(u, 0), 1), y1, y2);
|
||||
}
|
||||
|
||||
function fieldFor(p: ChladniParams, s: Shape): (x: number, y: number) => number {
|
||||
if (p.mode === 'plate') {
|
||||
const { a, b, n, m } = s;
|
||||
const rot = s.rotation ?? 0;
|
||||
const cos = Math.cos(rot), sin = Math.sin(rot);
|
||||
const [ox, oy] = p.origin;
|
||||
return (x, y) => {
|
||||
// Rotate about the window centre, so turning the plate keeps the figure in frame.
|
||||
const dx = x - ox, dy = y - oy;
|
||||
const u = ox + dx * cos - dy * sin;
|
||||
const v = oy + dx * sin + dy * cos;
|
||||
return a * Math.sin(Math.PI * n * u) * Math.sin(Math.PI * m * v) + b * Math.sin(Math.PI * m * u) * Math.sin(Math.PI * n * v);
|
||||
};
|
||||
}
|
||||
const pts: Point[] = Array.from({ length: p.sources }, (_, i) => {
|
||||
const t = (i / p.sources) * 2 * Math.PI - Math.PI / 2 + (s.spin ?? 0);
|
||||
return [p.origin[0] + s.radius * Math.cos(t), p.origin[1] + s.radius * Math.sin(t)];
|
||||
});
|
||||
const k = 2 * Math.PI * s.frequency;
|
||||
const inv = 1 / s.elasticity;
|
||||
return (x, y) => {
|
||||
let z = 0;
|
||||
for (const [sx, sy] of pts) z += Math.sin(k * (s.phase - inv * Math.hypot(x - sx, y - sy)));
|
||||
return z;
|
||||
};
|
||||
}
|
||||
|
||||
function segments(p: ChladniParams, shape: Shape, w: number, h: number): Point[][] {
|
||||
const q = p.quality;
|
||||
const height = p.length;
|
||||
const width = p.length * (w / h);
|
||||
const x0 = p.origin[0] - width / 2;
|
||||
const y0 = p.origin[1] - height / 2;
|
||||
const sx = w / width;
|
||||
const sy = h / height;
|
||||
const start = p.quadrant ? Math.ceil(q / 2) - 1 : 0;
|
||||
const f = fieldFor(p, shape);
|
||||
|
||||
const xs = new Float64Array(q);
|
||||
const ys = new Float64Array(q);
|
||||
for (let i = 0; i < q; i++) {
|
||||
xs[i] = x0 + (i * width) / (q - 1);
|
||||
ys[i] = y0 + (i * height) / (q - 1);
|
||||
}
|
||||
const z = new Float64Array(q * q);
|
||||
for (let r = start; r < q; r++) for (let c = start; c < q; c++) z[r * q + c] = f(xs[c], ys[r]);
|
||||
|
||||
const lerp = (v0: number, v1: number, p0: number, p1: number) =>
|
||||
Math.abs(v0 - v1) < 1e-10 ? (p0 + p1) / 2 : p0 + ((0 - v0) * (p1 - p0)) / (v1 - v0);
|
||||
const out: Point[][] = [];
|
||||
for (let r = start; r < q - 1; r++) {
|
||||
for (let c = start; c < q - 1; c++) {
|
||||
const tl = z[r * q + c], tr = z[r * q + c + 1], br = z[(r + 1) * q + c + 1], bl = z[(r + 1) * q + c];
|
||||
const k = (tl > 0 ? 1 : 0) | (tr > 0 ? 2 : 0) | (br > 0 ? 4 : 0) | (bl > 0 ? 8 : 0);
|
||||
if (k === 0 || k === 15) continue;
|
||||
const xa = xs[c], xb = xs[c + 1], ya = ys[r], yb = ys[r + 1];
|
||||
const top: Point = [(lerp(tl, tr, xa, xb) - x0) * sx, (ya - y0) * sy];
|
||||
const right: Point = [(xb - x0) * sx, (lerp(tr, br, ya, yb) - y0) * sy];
|
||||
const bottom: Point = [(lerp(bl, br, xa, xb) - x0) * sx, (yb - y0) * sy];
|
||||
const left: Point = [(xa - x0) * sx, (lerp(tl, bl, ya, yb) - y0) * sy];
|
||||
switch (k) {
|
||||
case 1: case 14: out.push([left, top]); break;
|
||||
case 2: case 13: out.push([top, right]); break;
|
||||
case 3: case 12: out.push([left, right]); break;
|
||||
case 4: case 11: out.push([right, bottom]); break;
|
||||
case 6: case 9: out.push([top, bottom]); break;
|
||||
case 7: case 8: out.push([left, bottom]); break;
|
||||
case 5: out.push([left, top], [right, bottom]); break;
|
||||
case 10: out.push([top, right], [left, bottom]); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
export function mountChladni(canvas: HTMLCanvasElement, trigger: HTMLElement, params: ChladniParams, reduceMotion: boolean) {
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) return;
|
||||
const keys: readonly string[] = params.mode === 'plate' ? PLATE_KEYS : SOURCE_KEYS;
|
||||
const source = params as unknown as Shape;
|
||||
// Every animatable key gets a number, 0 when the figure leaves it out (rotation, spin): a key
|
||||
// that only the hover set names would otherwise tween from undefined and blank the figure.
|
||||
const rest: Shape = {};
|
||||
for (const k of keys) rest[k] = typeof source[k] === 'number' ? source[k] : 0;
|
||||
const hover: Shape = { ...rest, ...(params.hover as Shape) };
|
||||
let shape: Shape = { ...rest };
|
||||
let frame = 0;
|
||||
|
||||
const draw = () => {
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
const w = Math.round(canvas.clientWidth * dpr);
|
||||
const h = Math.round(canvas.clientHeight * dpr);
|
||||
if (!w || !h) return;
|
||||
if (canvas.width !== w || canvas.height !== h) { canvas.width = w; canvas.height = h; }
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
ctx.strokeStyle = getComputedStyle(canvas).color;
|
||||
ctx.lineWidth = dpr;
|
||||
ctx.lineCap = 'round';
|
||||
ctx.beginPath();
|
||||
const segs = segments(params, shape, w, h);
|
||||
const mirrors: Point[] = params.quadrant ? [[1, 1], [-1, 1], [1, -1], [-1, -1]] : [[1, 1]];
|
||||
for (const [fx, fy] of mirrors) {
|
||||
for (const [p0, p1] of segs) {
|
||||
ctx.moveTo(fx === 1 ? p0[0] : w - p0[0], fy === 1 ? p0[1] : h - p0[1]);
|
||||
ctx.lineTo(fx === 1 ? p1[0] : w - p1[0], fy === 1 ? p1[1] : h - p1[1]);
|
||||
}
|
||||
}
|
||||
ctx.stroke();
|
||||
};
|
||||
|
||||
const tweenTo = (target: Shape) => {
|
||||
cancelAnimationFrame(frame);
|
||||
if (reduceMotion) { shape = { ...target }; draw(); return; }
|
||||
const from = { ...shape };
|
||||
const t0 = performance.now();
|
||||
const step = (now: number) => {
|
||||
const t = Math.min((now - t0) / DURATION, 1);
|
||||
const e = ease(t);
|
||||
const next: Shape = {};
|
||||
for (const k of Object.keys(target)) next[k] = from[k] + (target[k] - from[k]) * e;
|
||||
shape = next;
|
||||
draw();
|
||||
if (t < 1) frame = requestAnimationFrame(step);
|
||||
};
|
||||
frame = requestAnimationFrame(step);
|
||||
};
|
||||
|
||||
trigger.addEventListener('pointerenter', () => tweenTo(hover));
|
||||
trigger.addEventListener('pointerleave', () => tweenTo(rest));
|
||||
trigger.addEventListener('focus', () => tweenTo(hover));
|
||||
trigger.addEventListener('blur', () => tweenTo(rest));
|
||||
new ResizeObserver(draw).observe(canvas);
|
||||
draw();
|
||||
}
|
||||
@@ -10,6 +10,26 @@
|
||||
--night: #0E100F;
|
||||
--line: rgb(26 26 24 / 0.14);
|
||||
|
||||
/* Logo palette: only for the abstract fields (hero, research cards), never for text or UI. */
|
||||
--logo-deep: #284888;
|
||||
--logo-mid: #386898;
|
||||
--logo-teal: #48A8B8;
|
||||
--logo-cyan: #58C8D8;
|
||||
/* Neighbours of the logo blues for the research card fields, so the cards vary without leaving
|
||||
the brand: same coolness and depth, different hue. Fields only, never text or UI. */
|
||||
--field-indigo: #3B4096;
|
||||
--field-petrol: #1E5A63;
|
||||
--field-sea: #2E7C72;
|
||||
--field-steel: #4D6882;
|
||||
--field-dusk: #55508F;
|
||||
--field-cobalt: #2B56B0;
|
||||
--field-ocean: #1F4E86;
|
||||
--field-glacier: #7FB4CF;
|
||||
--field-aqua: #3EAFB2;
|
||||
--field-jade: #3B8C78;
|
||||
--field-slate: #3A4862;
|
||||
--field-plum: #66508E;
|
||||
|
||||
/* Immutable copies for resetting inside a .night or overlay context */
|
||||
--ink-base: #1A1A18;
|
||||
--ink-muted-base: #57564F;
|
||||
|
||||
Reference in new issue
Block a user