Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
W
www
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
1
Issues
1
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
PLN
www
Commits
5d9ce1dc
Commit
5d9ce1dc
authored
Nov 01, 2026
by
PLN (Algolia)
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
blog: hydra credit strip — CC badge icons, monochrome in every state, click-to-freeze
parent
f76b0587
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
127 additions
and
7 deletions
+127
-7
CcIcon.jsx
app/blog/CcIcon.jsx
+34
-0
HydraEmbed.jsx
app/blog/HydraEmbed.jsx
+65
-4
blog.module.css
app/blog/blog.module.css
+28
-3
No files found.
app/blog/CcIcon.jsx
0 → 100644
View file @
5d9ce1dc
// Creative Commons license badges — official icon path data from the
// cc-icons project (npm cc-icons, OFL-1.1; the icon set itself is CC's
// trademark, used here to indicate an actual CC license). Vendored inline
// so the credit strip never makes a remote request, and tinted by CSS
// (fill: currentColor) to stay monochrome over any sketch.
// ViewBox 5.5 -3.5 64 64 for all four.
const
VIEWBOX
=
'5.5 -3.5 64 64'
;
const
PATHS
=
{
cc
:
[
,
],
by
:
[
'M 31.943359 0 C 23.104359 0 15.601688 3.0848125 9.4296875 9.2578125 C 3.1446875 15.657813 0 23.239 0 32 C 0 40.762 3.1446875 48.284312 9.4296875 54.570312 C 15.790688 60.856312 23.294359 64 31.943359 64 C 40.742359 64 48.379469 60.818078 54.855469 54.455078 C 60.951469 48.475078 64 40.991 64 32 C 64 23.048 60.893547 15.467813 54.685547 9.2578125 C 48.513547 3.0858125 40.931359 0 31.943359 0 z M 32.056641 5.7714844 C 39.294641 5.7714844 45.466313 8.3237344 50.570312 13.427734 C 55.673312 18.533734 58.226562 24.722047 58.226562 31.998047 C 58.226563 39.389047 55.712594 45.503797 50.683594 50.341797 C 45.388594 55.562797 39.177687 58.169922 32.054688 58.169922 C 24.892688 58.169922 18.722016 55.580438 13.541016 50.398438 C 8.3610156 45.220437 5.7714844 39.087953 5.7714844 32.001953 C 5.7714844 24.954953 8.3796094 18.763687 13.599609 13.429688 C 18.628609 8.3256875 24.780641 5.7714844 32.056641 5.7714844 z M 32 11.314453 C 29.027 11.314453 27.542969 12.820125 27.542969 15.828125 C 27.542969 18.799125 29.029 20.285156 32 20.285156 C 34.971 20.285156 36.457031 18.799125 36.457031 15.828125 C 36.457031 12.818125 34.973 11.314453 32 11.314453 z M 25.429688 22 C 24.859688 22 24.373656 22.200609 23.972656 22.599609 C 23.573656 22.999609 23.373047 23.484641 23.373047 24.056641 L 23.373047 37.142578 L 27.029297 37.142578 L 27.029297 52.683594 L 36.972656 52.683594 L 36.972656 37.142578 L 40.628906 37.142578 L 40.628906 24.056641 C 40.628906 23.485641 40.418953 22.999609 40.001953 22.599609 C 39.583953 22.199609 39.105266 22 38.572266 22 L 25.429688 22 z'
,
],
nc
:
[
'M 31.941406 0 C 23.103406 0 15.600688 3.0868125 9.4296875 9.2578125 C 3.1446875 15.657812 0 23.238 0 32 C 0 40.725 3.1446875 48.228625 9.4296875 54.515625 C 15.752687 60.838625 23.255406 64 31.941406 64 C 40.740406 64 48.380469 60.819031 54.855469 54.457031 C 60.952469 48.475031 64 40.992 64 32 C 64 23.009 60.893547 15.427859 54.685547 9.2558594 C 48.513547 3.0848594 40.931406 0 31.941406 0 z M 32.056641 5.7148438 C 39.330641 5.7148437 45.505266 8.2847344 50.572266 13.427734 C 55.676266 18.457734 58.228516 24.648953 58.228516 32.001953 C 58.228516 34.325953 57.962734 36.609422 57.427734 38.857422 L 41.029297 31.544922 L 35.886719 29.259766 L 33.085938 28.001953 L 29.029297 26.171875 C 28.839297 25.904875 28.744141 25.638094 28.744141 25.371094 C 28.744141 24.457094 29.135016 23.810688 29.916016 23.429688 C 30.696016 23.049688 31.581266 22.857422 32.572266 22.857422 C 34.439266 22.857422 36.265594 23.485187 38.058594 24.742188 L 42.230469 20.457031 C 39.830469 18.629031 37.164469 17.639328 34.230469 17.486328 L 34.230469 12.570312 L 30.515625 12.570312 L 30.515625 17.486328 C 28.533625 17.601328 26.798453 18.162875 25.314453 19.171875 C 23.827453 20.180875 22.818156 21.523219 22.285156 23.199219 L 10.056641 17.714844 C 11.160641 16.113844 12.341609 14.685734 13.599609 13.427734 C 18.628609 8.2847344 24.780641 5.7148438 32.056641 5.7148438 z M 7.1992188 23.371094 L 23.542969 30.683594 L 28.628906 32.912109 L 32.400391 34.626953 L 35.484375 35.941406 C 36.055375 36.436406 36.34375 37.083766 36.34375 37.884766 C 36.34375 39.029766 35.951875 39.847797 35.171875 40.341797 C 34.389875 40.838797 33.445797 41.085938 32.341797 41.085938 C 29.674797 41.085938 27.291266 40.113875 25.197266 38.171875 L 20.798828 42.628906 C 23.730828 45.143906 26.968719 46.419078 30.511719 46.455078 L 30.511719 51.371094 L 34.226562 51.371094 L 34.226562 46.455078 C 36.435563 46.266078 38.348703 45.531859 39.970703 44.255859 C 41.588703 42.980859 42.606344 41.334453 43.027344 39.314453 L 55.082031 44.685547 C 54.016031 46.630547 52.552547 48.497109 50.685547 50.287109 C 45.464547 55.508109 39.255688 58.115234 32.054688 58.115234 C 24.931687 58.115234 18.760016 55.544391 13.541016 50.400391 C 8.3610156 45.220391 5.7714844 39.087953 5.7714844 32.001953 C 5.7714844 28.877953 6.2472188 25.999094 7.1992188 23.371094 z'
,
],
sa
:
[
'M 31.943359 0 C 23.143359 0 15.638687 3.1054062 9.4296875 9.3164062 C 3.1446875 15.716406 0 23.278953 0 32.001953 C 0 40.764953 3.1446875 48.285313 9.4296875 54.570312 C 15.790688 60.856313 23.294359 64 31.943359 64 C 40.779359 64 48.418469 60.837672 54.855469 54.513672 C 60.951469 48.456672 64 40.954 64 32 C 64 23.048 60.893547 15.486453 54.685547 9.3144531 C 48.474547 3.1044531 40.894359 0 31.943359 0 z M 32.056641 5.7714844 C 39.331641 5.7714844 45.503312 8.3413281 50.570312 13.486328 C 55.673312 18.514328 58.226562 24.687 58.226562 32 C 58.226563 39.353 55.712594 45.46775 50.683594 50.34375 C 45.388594 55.56275 39.177687 58.171875 32.054688 58.171875 C 24.892688 58.171875 18.722016 55.582391 13.541016 50.400391 C 8.3610156 45.257391 5.7714844 39.125 5.7714844 32 C 5.7714844 24.954 8.3796094 18.782328 13.599609 13.486328 C 18.628609 8.3433281 24.780641 5.7714844 32.056641 5.7714844 z M 31.826172 15.142578 C 28.168172 15.142578 25.065672 16.219094 22.513672 18.371094 C 19.960672 20.523094 18.380484 23.560375 17.771484 27.484375 L 15.259766 27.484375 L 21.601562 33.826172 L 27.943359 27.484375 L 25.601562 27.484375 C 26.134563 23.713375 28.532781 21.828125 32.800781 21.828125 C 35.164781 21.828125 36.972469 22.713375 38.230469 24.484375 C 39.486469 26.256375 40.115234 28.837516 40.115234 32.228516 C 40.115234 35.465516 39.429641 38.056047 38.056641 39.998047 C 36.685641 41.940047 34.874906 42.912109 32.628906 42.912109 C 28.132906 42.912109 25.789609 40.969984 25.599609 37.083984 L 17.541016 37.083984 C 18.075016 41.083984 19.654203 44.169797 22.283203 46.341797 C 24.911203 48.513797 28.035297 49.597656 31.654297 49.597656 C 36.570297 49.597656 40.56825 47.968891 43.65625 44.712891 C 46.74025 41.456891 48.283203 37.370078 48.283203 32.455078 C 48.283203 27.389078 46.797172 23.236047 43.826172 19.998047 C 40.855172 16.762047 36.853172 15.142578 31.826172 15.142578 z'
,
],
};
/** One CC badge circle — `kind` is 'cc' | 'by' | 'nc' | 'sa'. */
export
default
function
CcIcon
({
kind
,
size
=
14
})
{
return
(
<
svg
viewBox=
{
VIEWBOX
}
width=
{
size
}
height=
{
size
}
fill=
"currentColor"
aria
-
hidden=
"true"
>
{
PATHS
[
kind
].
map
((
d
)
=>
(
<
path
key=
{
d
.
slice
(
0
,
24
)
}
d=
{
d
}
/>
))
}
</
svg
>
);
}
app/blog/HydraEmbed.jsx
View file @
5d9ce1dc
...
...
@@ -2,6 +2,7 @@
import
{
useEffect
,
useRef
,
useState
}
from
'react'
;
import
styles
from
'./blog.module.css'
;
import
CcIcon
from
'./CcIcon'
;
/**
* A Hydra sketch island at the opening/closing of a post. It boots on page
...
...
@@ -16,11 +17,32 @@ import styles from './blog.module.css';
* hydra-synth instance; the last one booted owns the global generators,
* which is the right trade for a page that books both ends.
*/
/** The CC badge circles a license name declares: 'CC BY-NC-SA 4.0' → cc, by, nc, sa. */
function
CcBadges
({
license
})
{
const
kinds
=
[
'cc'
];
for
(
const
part
of
license
.
split
(
/
\s
+/
).
slice
(
1
))
{
for
(
const
token
of
part
.
split
(
'-'
))
{
const
kind
=
token
.
toLowerCase
();
if
([
'by'
,
'nc'
,
'sa'
,
'nd'
].
includes
(
kind
))
kinds
.
push
(
kind
);
}
}
return
(
<
span
className=
{
styles
.
hydraBadges
}
aria
-
hidden=
"true"
>
{
kinds
.
map
((
kind
)
=>
(
<
CcIcon
key=
{
kind
}
kind=
{
kind
}
/>
))
}
</
span
>
);
}
export
default
function
HydraEmbed
({
sketch
,
lang
})
{
const
[
running
,
setRunning
]
=
useState
(
false
);
const
[
paused
,
setPaused
]
=
useState
(
false
);
const
[
failed
,
setFailed
]
=
useState
(
false
);
const
canvasRef
=
useRef
(
null
);
const
stageRef
=
useRef
(
null
);
const
hydraRef
=
useRef
(
null
);
const
savedSpeedRef
=
useRef
(
1
);
useEffect
(()
=>
{
if
(
window
.
matchMedia
(
'(prefers-reduced-motion: reduce)'
).
matches
)
return
;
...
...
@@ -40,6 +62,7 @@ export default function HydraEmbed({ sketch, lang }) {
canvas
.
width
=
width
;
canvas
.
height
=
height
;
hydra
=
new
Hydra
({
canvas
,
detectAudio
:
false
,
width
,
height
});
hydraRef
.
current
=
hydra
;
hydra
.
eval
(
sketch
.
source
);
})
.
catch
(()
=>
{
...
...
@@ -47,18 +70,47 @@ export default function HydraEmbed({ sketch, lang }) {
});
return
()
=>
{
cancelled
=
true
;
hydraRef
.
current
=
null
;
if
(
hydra
&&
typeof
hydra
.
close
===
'function'
)
hydra
.
close
();
};
},
[
running
,
sketch
.
source
]);
/** Click on a running sketch freezes time (speed → 0); click again resumes. */
const
togglePause
=
()
=>
{
const
hydra
=
hydraRef
.
current
;
if
(
!
hydra
)
return
;
if
(
paused
)
{
hydra
.
sandbox
.
set
(
'speed'
,
savedSpeedRef
.
current
);
setPaused
(
false
);
}
else
{
savedSpeedRef
.
current
=
hydra
.
synth
.
speed
||
1
;
hydra
.
sandbox
.
set
(
'speed'
,
0
);
setPaused
(
true
);
}
};
const
hint
=
lang
===
'en'
?
'Run the sketch'
:
'Lancer le sketch'
;
const
codeLabel
=
lang
===
'en'
?
'sketch source'
:
'
source du sketch
'
;
const
codeLabel
=
lang
===
'en'
?
'sketch source'
:
'
code source
'
;
return
(
<
figure
className=
{
styles
.
hydra
}
>
<
div
ref=
{
stageRef
}
className=
{
styles
.
hydraStage
}
>
{
running
?
(
<
canvas
ref=
{
canvasRef
}
className=
{
styles
.
hydraCanvas
}
/>
<
canvas
ref=
{
canvasRef
}
className=
{
styles
.
hydraCanvas
}
onClick=
{
togglePause
}
role=
"button"
tabIndex=
{
0
}
onKeyDown=
{
(
e
)
=>
{
if
(
e
.
key
===
'Enter'
||
e
.
key
===
' '
)
{
e
.
preventDefault
();
togglePause
();
}
}
}
aria
-
label=
{
paused
?
(
lang
===
'en'
?
'Resume the sketch'
:
'Relancer le sketch'
)
:
lang
===
'en'
?
'Freeze the sketch'
:
'Figer le sketch'
}
title=
{
paused
?
(
lang
===
'en'
?
'Click to resume'
:
'Cliquer pour relancer'
)
:
lang
===
'en'
?
'Click to freeze'
:
'Cliquer pour figer'
}
/>
)
:
(
<
a
href=
{
sketch
.
url
}
...
...
@@ -81,11 +133,20 @@ export default function HydraEmbed({ sketch, lang }) {
{
sketch
.
author
&&
<
span
className=
{
styles
.
hydraAuthor
}
>
{
sketch
.
author
}
</
span
>
}
{
sketch
.
license
&&
(
sketch
.
licenseUrl
?
(
<
a
href=
{
sketch
.
licenseUrl
}
target=
"_blank"
rel=
"license noopener noreferrer"
>
<
a
href=
{
sketch
.
licenseUrl
}
target=
"_blank"
rel=
"license noopener noreferrer"
className=
{
styles
.
hydraLicense
}
>
<
CcBadges
license=
{
sketch
.
license
}
/>
{
sketch
.
license
}
</
a
>
)
:
(
<
span
>
{
sketch
.
license
}
</
span
>
<
span
className=
{
styles
.
hydraLicense
}
>
<
CcBadges
license=
{
sketch
.
license
}
/>
{
sketch
.
license
}
</
span
>
))
}
<
a
href=
{
sketch
.
url
}
target=
"_blank"
rel=
"noopener noreferrer"
>
hydra.ojack.xyz ↗
...
...
app/blog/blog.module.css
View file @
5d9ce1dc
...
...
@@ -637,6 +637,7 @@
}
.hydraCanvas
{
background
:
#000
;
cursor
:
pointer
;
}
.hydraFacade
{
display
:
flex
;
...
...
@@ -677,12 +678,16 @@
padding
:
0.4rem
0.9rem
;
font-size
:
0.78rem
;
/* Strictly monochrome — this strip floats over the sketch's output, so
theme tokens (which flip with the site's light/dark) would betray it. */
theme tokens (which flip with the site's light/dark) would betray it.
Two classes to out-rank main.css's `.site-shell footer` color. */
color
:
rgba
(
255
,
255
,
255
,
0.85
);
background
:
linear-gradient
(
transparent
,
rgba
(
0
,
0
,
0
,
0.78
));
opacity
:
0
;
transition
:
opacity
0.2s
ease
;
}
.hydra
.hydraFooter
{
color
:
rgba
(
255
,
255
,
255
,
0.85
);
}
.hydraStage
:hover
.hydraFooter
,
.hydraStage
:focus-within
.hydraFooter
{
opacity
:
1
;
...
...
@@ -700,10 +705,30 @@
text-decoration
:
underline
;
text-underline-offset
:
2px
;
}
.hydraFooter
a
:visited
,
.hydraFooter
a
:hover
{
/* Monochrome in every state, at raised specificity: the global sheets set
link colors on a/`a:hover` at (0,1,1) and load after modules, so an equal
specificity here loses the moment the reader hovers — which is exactly
when this strip is visible. */
.hydra
.hydraFooter
a
:link
,
.hydra
.hydraFooter
a
:visited
,
.hydra
.hydraFooter
a
:hover
,
.hydra
.hydraFooter
a
:focus-visible
,
.hydra
.hydraFooter
a
:active
{
color
:
rgba
(
255
,
255
,
255
,
0.85
);
}
.hydraLicense
{
display
:
inline-flex
;
align-items
:
center
;
gap
:
0.4rem
;
}
.hydraBadges
{
display
:
inline-flex
;
align-items
:
center
;
gap
:
2px
;
}
.hydraBadges
svg
{
display
:
block
;
}
.hydraCode
{
margin-top
:
0.4rem
;
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment