mirror of
https://codeberg.org/simonrepp/hyper8.git
synced 2026-08-14 13:45:27 +02:00
Iterate on layout, design, outlines and contextual elements
This commit is contained in:
+35
-43
@@ -14,6 +14,7 @@ a:hover { color: var(--fg-3); }
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
padding: .3rem .6rem .3rem .5rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.actions a:focus-visible,
|
||||
.actions a:hover,
|
||||
@@ -62,6 +63,17 @@ button {
|
||||
text-underline-offset: .3rem;
|
||||
}
|
||||
.center .description a:hover { text-decoration: none; }
|
||||
.context_dot {
|
||||
border-radius: 50%;
|
||||
display: block;
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
}
|
||||
.context_dot:focus-visible,
|
||||
.context_dot:hover {
|
||||
box-shadow: 0 0 .15rem var(--mg);
|
||||
}
|
||||
.context_dot + .context_dot { margin-left: .3rem; }
|
||||
#copy-link::after {
|
||||
color: var(--fg-2);
|
||||
padding-left: .5rem;
|
||||
@@ -93,12 +105,13 @@ button {
|
||||
}
|
||||
.flex { display: flex; }
|
||||
.flex.align { align-items: center; }
|
||||
.flex.align_start { align-items: flex-start; }
|
||||
.flex.apart { justify-content: space-between; }
|
||||
.flex.space { column-gap: .3rem; }
|
||||
.flex.wrap { flex-wrap: wrap; }
|
||||
footer {
|
||||
align-items: center;
|
||||
background: var(--bg-1);
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
font-size: .9rem;
|
||||
flex-wrap: wrap;
|
||||
@@ -191,34 +204,25 @@ img {
|
||||
padding: .5rem;
|
||||
}
|
||||
.medium_font { font-size: 1.2rem; }
|
||||
/*.outline {
|
||||
.parent_context {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
font-size: .9rem;
|
||||
gap: .3rem;
|
||||
margin-bottom: .2rem;
|
||||
}
|
||||
.parent_context a:not(.context_dot) {
|
||||
align-items: center;
|
||||
column-gap: .3rem;
|
||||
display: flex;
|
||||
}
|
||||
.parent_context > div {
|
||||
align-items: flex-end;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 1rem;
|
||||
gap: .3rem;
|
||||
}
|
||||
.outline > a {
|
||||
border: 1px solid var(--bg-2);
|
||||
border-radius: .6rem;
|
||||
position: relative;
|
||||
}
|
||||
.outline > a > img,
|
||||
.outline > a > .placeholder {
|
||||
border-radius: .6rem;
|
||||
}
|
||||
.outline > a > .placeholder {
|
||||
background: var(--bg-2);
|
||||
height: 12rem;
|
||||
}
|
||||
.outline .text {
|
||||
background: var(--bg-1-85);
|
||||
border-bottom-left-radius: .6rem;
|
||||
border-bottom-right-radius: .6rem;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
padding: 1rem;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}*/
|
||||
.parent_context > div > div:nth-child(2) { display: flex; }
|
||||
.parents {
|
||||
font-size: .9rem;
|
||||
margin-top: .2rem;
|
||||
@@ -346,12 +350,7 @@ img {
|
||||
@media (max-aspect-ratio: 2/3) {
|
||||
.player_wrapper:not(:fullscreen) video { max-height: 76dvh; }
|
||||
}
|
||||
.playlist_context {
|
||||
border: 1px solid var(--bg-2);
|
||||
border-radius: .5rem;
|
||||
/* TODO: Shadows only in light mode or different strategy between both color schemes */
|
||||
box-shadow: 0 0 12rem #0001;
|
||||
}
|
||||
.playlist_context { margin-top: 6.1rem; }
|
||||
.playlist_context > a {
|
||||
display: flex;
|
||||
outline: none;
|
||||
@@ -370,7 +369,7 @@ img {
|
||||
.playlist_context > a:focus-visible,
|
||||
.playlist_context > a:hover { background: var(--bg-2); }
|
||||
.playlist_context > a:not(:last-child) {
|
||||
border-bottom: 1px solid var(--bg-1);
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
.playlist_context .current { background: var(--bg-3); }
|
||||
.playlist_context .description {
|
||||
@@ -416,15 +415,6 @@ img {
|
||||
height: 4rem;
|
||||
object-fit: contain;
|
||||
}
|
||||
.playlist_header {
|
||||
background: var(--bg-1);
|
||||
padding: .8rem;
|
||||
}
|
||||
.playlist_header .duration { color: var(--fg-3); }
|
||||
.playlist_header .title {
|
||||
color: var(--fg-1);
|
||||
display: block;
|
||||
}
|
||||
.playlist_page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -667,15 +657,17 @@ svg {
|
||||
}
|
||||
.versions_ring.interacting g:not(:focus-visible):not(:hover) { opacity: .5; }
|
||||
video {
|
||||
background: var(--d-1);
|
||||
background: #000;
|
||||
border-radius: .5rem;
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
.video_info { margin-bottom: 1.5rem; }
|
||||
.video_title {
|
||||
color: var(--fg-1);
|
||||
margin: 0;
|
||||
margin-bottom: .2em;
|
||||
}
|
||||
.videos {
|
||||
column-gap: 1rem;
|
||||
|
||||
@@ -134,21 +134,6 @@ pub fn collection_html(
|
||||
body.push_str(&rendered);
|
||||
}
|
||||
|
||||
// if traversal.steps.len() > 1 {
|
||||
// let outline_items = traversal.steps
|
||||
// .iter()
|
||||
// .filter(|step| !step.is_collection(collection))
|
||||
// .enumerate()
|
||||
// .map(|(index, step)| {
|
||||
// let prefix = "../".repeat(traversal.steps.len() - 1 - index);
|
||||
// step.context_for_collection(collection, &prefix, site)
|
||||
// })
|
||||
// .collect::<Vec<String>>()
|
||||
// .join("\n");
|
||||
|
||||
// body.push_str(&format!(r#"<div class="outline">{outline_items}</div>"#));
|
||||
// }
|
||||
|
||||
if collection.platform_integration().enabled() {
|
||||
if let Some(collection_url) = site.absolute_url_index(&collection.site_path) {
|
||||
let mut meta = PlatformIntegrationMeta::new(title.to_string(), collection_url);
|
||||
|
||||
+81
-30
@@ -24,8 +24,6 @@ pub struct Breadcrumb<'a> {
|
||||
pub step: &'a TraversalStep<'a>
|
||||
}
|
||||
|
||||
// TODO: Consider the similarity to TraversalStep here (!) This indicates
|
||||
// we should really have a generic item of that sort for representing both?
|
||||
pub enum CurrentPage<'a> {
|
||||
Collection(&'a Collection),
|
||||
Playlist(&'a Playlist),
|
||||
@@ -438,6 +436,72 @@ impl TraversalStep<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn context_with_bubbles(
|
||||
&self,
|
||||
prefix: &str,
|
||||
site: &Site
|
||||
) -> String {
|
||||
let index_suffix_file_only = site.index_suffix_file_only();
|
||||
let translations = &site.language.translations;
|
||||
|
||||
match self {
|
||||
TraversalStep::Collection(collection) => {
|
||||
let label = collection.title_or_slug_or_generic_label(translations);
|
||||
let label_escaped = html_escape_outside_attribute(label);
|
||||
|
||||
let video_bubbles = collection.public_videos()
|
||||
.map(|video| {
|
||||
if let Some(poster_meta) = &video.poster.as_ref().and_then(|poster| poster.poster_meta.as_ref()) {
|
||||
let background = poster_meta.lqip.gradient_radial();
|
||||
let video_slug = video.slug_unchecked();
|
||||
formatdoc!(r#"<a class="context_dot" href="{prefix}{video_slug}/" style="{background}"></a>"#)
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join("");
|
||||
|
||||
let count = collection.public_videos().count();
|
||||
|
||||
format!(r#"
|
||||
<div>
|
||||
<div><a href="{prefix}{index_suffix_file_only}">{label_escaped}</a></div>
|
||||
<div style="--count: {count}">{video_bubbles}</div>
|
||||
</div>
|
||||
"#)
|
||||
}
|
||||
TraversalStep::Playlist(playlist) => {
|
||||
let label = playlist.title_or_slug_or_generic_label(translations);
|
||||
let label_escaped = html_escape_outside_attribute(label);
|
||||
|
||||
let video_bubbles = playlist.public_videos()
|
||||
.map(|video| {
|
||||
if let Some(poster_meta) = &video.poster.as_ref().and_then(|poster| poster.poster_meta.as_ref()) {
|
||||
let background = poster_meta.lqip.gradient_radial();
|
||||
let video_slug = video.slug_unchecked();
|
||||
formatdoc!(r#"<a class="context_dot" href="{prefix}{video_slug}/" style="{background}"></a>"#)
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join("");
|
||||
|
||||
let count = playlist.public_videos().count();
|
||||
|
||||
format!(r#"
|
||||
<div>
|
||||
<div><a href="{prefix}{index_suffix_file_only}">{label_escaped}</a></div>
|
||||
<div style="--count: {count}">{video_bubbles}</div>
|
||||
</div>
|
||||
"#)
|
||||
}
|
||||
TraversalStep::Video(_) => unimplemented!(),
|
||||
TraversalStep::Virtual { .. } => unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
fn crumb_collection_header(
|
||||
collection: &Collection,
|
||||
prefix: &str,
|
||||
@@ -655,27 +719,19 @@ impl TraversalStep<'_> {
|
||||
site: &Site,
|
||||
video: Option<&Video>
|
||||
) -> String {
|
||||
let banner = if let Some(banner) = &playlist.banner {
|
||||
banner_html(banner, prefix)
|
||||
} else if let Some(video) = &playlist.public_videos()
|
||||
.find(|video| video.poster.is_some()) {
|
||||
let video_prefix = format!("{prefix}{}/", video.slug_unchecked());
|
||||
poster_html(video.poster.as_ref().unwrap(), &video_prefix)
|
||||
} else {
|
||||
String::from(r#"<div class="placeholder"></div>"#)
|
||||
};
|
||||
let label = playlist.title_or_slug_or_generic_label(&site.language.translations);
|
||||
let outline_stats = OutlineStats::for_playlist(playlist).render(site);
|
||||
let translations = &site.language.translations;
|
||||
|
||||
let header_crumb = formatdoc!(r#"
|
||||
<a href="{prefix}">
|
||||
{banner}
|
||||
<div class="text">
|
||||
<div class="medium_font">{label}</div>
|
||||
<small>{outline_stats}</small>
|
||||
</div>
|
||||
</a>
|
||||
"#);
|
||||
// let banner = if let Some(banner) = &playlist.banner {
|
||||
// banner_html(banner, prefix)
|
||||
// } else if let Some(video) = &playlist.public_videos()
|
||||
// .find(|video| video.poster.is_some()) {
|
||||
// let video_prefix = format!("{prefix}{}/", video.slug_unchecked());
|
||||
// poster_html(video.poster.as_ref().unwrap(), &video_prefix)
|
||||
// } else {
|
||||
// String::from(r#"<div class="placeholder"></div>"#)
|
||||
// };
|
||||
|
||||
let label = playlist.title_or_slug_or_generic_label(translations);
|
||||
|
||||
if let Some(video) = video {
|
||||
let index_suffix = site.index_suffix();
|
||||
@@ -686,7 +742,7 @@ impl TraversalStep<'_> {
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, video_iterated)| {
|
||||
let label = video_iterated.title_or_slug_or_generic_label(&site.language.translations);
|
||||
let label = video_iterated.title_or_slug_or_generic_label(translations);
|
||||
let slug = video_iterated.slug_unchecked();
|
||||
|
||||
let video_prefix = if video_iterated.site_path == video.site_path {
|
||||
@@ -725,18 +781,13 @@ impl TraversalStep<'_> {
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n");
|
||||
|
||||
let videos_crumb = formatdoc!(r#"
|
||||
formatdoc!(r#"
|
||||
<div class="playlist_context">
|
||||
{items}
|
||||
</div>
|
||||
"#);
|
||||
|
||||
formatdoc!(r#"
|
||||
{header_crumb}
|
||||
{videos_crumb}
|
||||
"#)
|
||||
} else {
|
||||
header_crumb
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -120,21 +120,6 @@ pub fn playlist_html(
|
||||
body.push_str(&videos_rendered);
|
||||
}
|
||||
|
||||
// if !traversal.steps.is_empty() {
|
||||
// let outline_items = traversal.steps
|
||||
// .iter()
|
||||
// .filter(|step| !step.is_playlist())
|
||||
// .enumerate()
|
||||
// .map(|(index, step)| {
|
||||
// let prefix = "../".repeat(traversal.steps.len() - 1 - index);
|
||||
// step.context_for_playlist(playlist, &prefix, site)
|
||||
// })
|
||||
// .collect::<Vec<String>>()
|
||||
// .join("\n");
|
||||
|
||||
// body.push_str(&format!(r#"<div class="outline">{outline_items}</div>"#));
|
||||
// }
|
||||
|
||||
if playlist.platform_integration().enabled() {
|
||||
if let Some(playlist_url) = site.absolute_url_index(&playlist.site_path) {
|
||||
let mut meta = PlatformIntegrationMeta::new(title.to_string(), playlist_url);
|
||||
|
||||
+19
-18
@@ -85,22 +85,18 @@ pub fn video_html(
|
||||
}
|
||||
};
|
||||
|
||||
// let outline_items = traversal.steps
|
||||
// .iter()
|
||||
// .filter(|step| !step.is_video(video))
|
||||
// .enumerate()
|
||||
// .map(|(index, step)| {
|
||||
// let prefix = "../".repeat(traversal.steps.len() - 1 - index);
|
||||
// step.context_for_video(&prefix, site, video)
|
||||
// })
|
||||
// .collect::<Vec<String>>()
|
||||
// .join("\n");
|
||||
let parent_context = if traversal.steps.len() > 1 {
|
||||
let parent_step = &traversal.steps[traversal.steps.len() - 2];
|
||||
let context_with_bubbles = parent_step.context_with_bubbles("../", site);
|
||||
|
||||
// let outline_rendered = formatdoc!(r#"
|
||||
// <div class="outline">
|
||||
// {outline_items}
|
||||
// </div>
|
||||
// "#);
|
||||
formatdoc!(r#"
|
||||
<div class="parent_context">
|
||||
{context_with_bubbles}
|
||||
</div>
|
||||
"#)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let video_prefix = "";
|
||||
let player_rendered = player(false, next_video, site, video, video_prefix);
|
||||
@@ -152,11 +148,16 @@ pub fn video_html(
|
||||
|
||||
let video_content = formatdoc!(r#"
|
||||
<div>
|
||||
<div class="video_info">
|
||||
<div class="align apart flex space wrap" style="margin-top: 2rem;">
|
||||
<h1 class="video_title">{e_title} {r_unlisted_badge}</h1>
|
||||
{parent_context}
|
||||
</div>
|
||||
</div>
|
||||
{player_rendered}
|
||||
<div class="video_info">
|
||||
<div class="align apart flex" style="margin-top: 2rem;">
|
||||
<div class="align_start apart flex wrap" style="margin-top: 2rem;">
|
||||
<div>
|
||||
<h1 class="video_title">{e_title} {r_unlisted_badge}</h1>
|
||||
{release_date}
|
||||
</div>
|
||||
<div class="actions align flex space">
|
||||
@@ -165,8 +166,8 @@ pub fn video_html(
|
||||
{r_downloads}
|
||||
</div>
|
||||
</div>
|
||||
{description}
|
||||
{links_rendered}
|
||||
{description}
|
||||
</div>
|
||||
</div>
|
||||
"#);
|
||||
|
||||
@@ -532,6 +532,13 @@ impl Site {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn index_suffix_file_only(&self) -> &str {
|
||||
match self.clean_urls {
|
||||
true => "",
|
||||
false => "index.html"
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new() -> Site {
|
||||
Site {
|
||||
base_url: None,
|
||||
|
||||
Reference in New Issue
Block a user