diff options
| author | jade (winter) | 2025-12-05 00:34:03 +0000 |
|---|---|---|
| committer | jade (winter) | 2025-12-05 00:34:03 +0000 |
| commit | 92d27365cadbd98e6d709673cd0729de8793c494 (patch) | |
| tree | e7ac18fab25957d39745a865ef269f6b6733f15a | |
initial commit
| -rw-r--r-- | index.php | 36 | ||||
| -rw-r--r-- | lib/bbcnext.php | 109 | ||||
| -rw-r--r-- | lib/rms.php | 10 | ||||
| -rw-r--r-- | search.php | 3 | ||||
| -rw-r--r-- | theme/Brutalist/main.css | 198 | ||||
| -rw-r--r-- | theme/Default/main.css | 250 |
6 files changed, 606 insertions, 0 deletions
diff --git a/index.php b/index.php new file mode 100644 index 0000000..b4a98e7 --- /dev/null +++ b/index.php @@ -0,0 +1,36 @@ +<?php +require_once __DIR__ . '/lib/bbcnext.php'; + +stream_context_set_default(['http' => [ + 'user_agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36', +]]); +?> +<!doctype html> +<html lang="en-GB"> + <head> + <meta charset="UTF-8"/> + <meta name="viewport" content="width=device-width,initial-scale=1"/> + <title></title> + <?php foreach (array_diff(scandir(__DIR__ . '/theme'), ['.', '..']) as $theme): ?> + <link rel="<?=$theme=='Default'?'':'alternate '?>stylesheet" + href="theme/<?=$theme?>/main.css" + title="<?=$theme?>"/> + <?php endforeach; ?> + </head> + + <body> + <header> + <a href="."><frontend-name>RMS Frontend</frontend-name></a> + <form id="search-form" action="search.php"> + <input name="q" placeholder="Query…"/> + <input type="submit" value="Search"/> + </form> + </header> + + <main> + <?php + next_renderFromRms($_GET['path'] ?? RMS_SOUNDS_HOME); + ?> + </main> + </body> +</html> diff --git a/lib/bbcnext.php b/lib/bbcnext.php new file mode 100644 index 0000000..988d248 --- /dev/null +++ b/lib/bbcnext.php @@ -0,0 +1,109 @@ +<?php + +require_once __DIR__ . '/rms.php'; + +function next_renderFromRms(string $path) { + next_renderExperience(rms_fetch($path)->data); +} + +function next_renderExperience(array $modules) { + foreach ($modules as $m) { + next_renderModule($m); + } +} + +function next_renderModule(stdClass $module) { + $type = strtr($module->type, '_', '-'); + $titleTag = $type == 'inline-header-module' ? 'h1' : 'h2'; + echo "<$type id=\"$module->id\" module-style=\"$module->style\">"; + echo "<$titleTag>" + . htmlspecialchars($module->title ?? '[no title]') + . "</$titleTag>"; + if (isset($module->description)) { + echo '<module-description>' + . htmlspecialchars($module->description) + . '</module-description>'; + } + echo '<module-items>'; + if (!is_array($module->data)) { + next_renderItem($module->data); + } else { + foreach ($module->data as $item) { + next_renderItem($item); + } + } + echo "</module-items></$type>"; +} + +function next_renderItem(stdClass $item) { + $type = strtr($item->type, '_', '-'); + $primaryTag = $type == 'segment-item' ? 'primary-title' : 'h3'; + echo "<$type id=\"$item->id\">"; + echo '<item-titles><item-first-titles>'; + echo "<$primaryTag>" + . htmlspecialchars($item->titles?->primary ?? '[no primary]') + . "</$primaryTag>"; + if (isset($item->network)) { + echo '<item-network>' + . htmlspecialchars($item->network->short_title) + . '</item-network>'; + } + echo '</item-first-titles>'; + if (isset($item->titles->secondary)) { + echo '<secondary-title>' + . htmlspecialchars($item->titles->secondary) + . '</secondary-title>'; + } + if (isset($item->titles->tertiary)) { + echo '<tertiary-title>' + . htmlspecialchars($item->titles->tertiary) + . '</tertiary-title>'; + } + echo '</item-titles>'; + if (isset($item->synopses?->short, $item->synopses?->medium)) { + echo '<synopsis><details><summary>' + . htmlspecialchars($item->synopses->short) + . '</summary>' + . htmlspecialchars($item->synopses->long ?? $item->synopses->medium) + . '</details></synopsis>'; + } elseif (isset($item->synopses?->short)) { + echo '<synopsis>' . htmlspecialchars($item->synopses->short) + . '</synopsis>'; + } + + if (isset($item->item)) { + next_renderItem($item->item); + } + + if (isset($item->offset)) { + echo '<offset-start>' + . gmdate('H:i:s', $item->offset->start) + . '</offset-start>'; + echo '<offset-end>' + . gmdate('H:i:s', $item->offset->end) + . '</offset-end>'; + } + + if ($type == 'container-item') { + $href = '?path=' . RMS_SOUNDS_CONTAINER . '/' . $item->urn; + echo "<a href=\"$href\">View</a>"; + } elseif ($type == 'playable-item') { + $pid = explode(':', $item->urn)[4]; + $href = '?path=' . RMS_SOUNDS_PLAYABLE . '/' . $pid; + echo "<a href=\"$href\">Listen</a>"; + } + + foreach ($item->uris ?? [] as $uri) { + if ($uri->type != 'link') continue; + echo '<a href="' . htmlspecialchars($uri->uri) . '" target="_blank">' + . htmlspecialchars($uri->label) . '</a>'; + } + + if (isset($item->download) && $item->download->type == 'non_drm') { + $variant = $item->download->quality_variants->high; + echo '<a href="' . htmlspecialchars($variant->file_url) . '">' + . "Download ($variant->label)</a>"; + } + + echo "</$type>"; +} diff --git a/lib/rms.php b/lib/rms.php new file mode 100644 index 0000000..1e76d53 --- /dev/null +++ b/lib/rms.php @@ -0,0 +1,10 @@ +<?php + +const RMS_API_BASE = 'https://rms.api.bbc.co.uk/v2'; +const RMS_SOUNDS_HOME = '/experience/inline/listen/sign-in'; +const RMS_SOUNDS_CONTAINER = '/experience/inline/container'; +const RMS_SOUNDS_PLAYABLE = '/experience/inline/play'; + +function rms_fetch(string $path): stdClass { + return json_decode(file_get_contents(RMS_API_BASE . $path)); +} diff --git a/search.php b/search.php new file mode 100644 index 0000000..ad52c35 --- /dev/null +++ b/search.php @@ -0,0 +1,3 @@ +<?php + +header('Location: .?path=/experience/inline/search?q=' . urlencode($_GET['q'])); diff --git a/theme/Brutalist/main.css b/theme/Brutalist/main.css new file mode 100644 index 0000000..52f82d2 --- /dev/null +++ b/theme/Brutalist/main.css @@ -0,0 +1,198 @@ +:root { + font-family: sans-serif; + background: #eee; +} + +body { + margin: 0; +} + +main { + width: 100vw; + display: flex; + align-items: center; + flex-direction: column; +} + +header { + box-sizing: border-box; + width: calc(100vw - 64px); + background: black; + margin: 32px; + padding: 16px; + color: white; + display: grid; + grid-auto-flow: column; + grid-auto-columns: 1fr auto; + align-items: center; + + a { + color: white; + } + + frontend-name { + font-size: 0; + &::after { + font-size: large; + font-weight: bold; + content: "Brutalist Sounds"; + } + } + + #search-form { + input { + background: black; + border: 1px solid white; + color: white; + font-family: inherit; + font-size: small; + padding: 4px 8px; + &::placeholder { + color: #bbb; + } + } + } +} + +inline-display-module { + border: 1px solid black; + margin: 16px; + padding: 16px; + display: flex; + flex-direction: column; + gap: 16px; + height: fit-content; + max-height: 600px; + overflow-y: scroll; + box-sizing: border-box; + width: min(1000px, calc(100vw - 64px)); + background: white; + color: black; + + h2 { + top: -16px; + margin: -16px; + margin-bottom: 0; + position: sticky; + background: black; + color: white; + padding: 8px; + padding-left: 16px; + } + + module-description { + font-weight: bold; + } +} + +inline-header-module { + display: block; + padding: 16px; + + h2 { + font-size: xx-large; + } + + container-item { + display: none; + } +} + +playable-item, container-item, display-item, broadcast-summary { + display: block; + padding: 8px; + + item-first-titles { + margin: -8px; + margin-bottom: 8px; + background: black; + color: white; + padding: 0 8px; + display: grid; + grid-auto-flow: column; + grid-auto-columns: 1fr auto; + align-items: baseline; + } + + h3 { + margin: 0; + } + + secondary-title { + display: block; + font-weight: bold; + } + + tertiary-title { + display: block; + font-style: italic; + } + + synopsis { + display: block; + margin-top: 8px; + white-space: pre-wrap; + + details { + summary { + list-style: none; + &::after { + font-weight: bold; + content: ' Show more…'; + } + } + &[open] { + summary { + display: none; + } + } + } + } + + a { + font-style: italic; + display: block; + text-align: end; + &::after { + content: " »"; + } + } +} + +#aod_play_area, #single_item_promo { +padding: 0; +gap: 0; +h2 { + margin: 0px; +} +playable-item, display-item { + h3, secondary-title, tertiary-title, synopsis { + padding: 0 8px; + } + + a { + display: none; + } +} +} + +segment-item { + display: grid; + gap: 8px; + grid-auto-flow: column; + grid-auto-columns: 1fr auto auto; + border-bottom: 0.5px dashed black; + + primary-title { + font-weight: bold; + margin-inline-end: 12px; + } + tertiary-title { + display: none; + } + + offset-start::after { + margin-inline-start: 8px; + content: '—'; + } +} diff --git a/theme/Default/main.css b/theme/Default/main.css new file mode 100644 index 0000000..b2892fb --- /dev/null +++ b/theme/Default/main.css @@ -0,0 +1,250 @@ +body { + background: #eee; + display: flex; + flex-direction: column; + align-items: center; + font-size: 10pt; + margin: 0; + padding: 16px; + gap: 16px; + font-family: sans-serif; +} + +a { + text-decoration: none; + color: #8a6040; +} + +header, main { + width: min(800px, calc(100vw - 32px)); + box-sizing: border-box; + background: white; + border-radius: 4px; + overflow: clip; + border: 2px solid grey; +} + +header { + display: grid; + grid-auto-flow: column; + grid-auto-columns: 1fr auto; + align-items: center; + background: #963; + + > * { + padding: 8px; + } + + a { + background: linear-gradient(to right, #ca9a7a, #963); + display: block; + color: white; + font-weight: bold; + width: max-content; + font-size: 0; + &::after { + font-size: 16pt; + content: 'BBC Sounds'; + } + } +} + +inline-display-module { + display: flex; + flex-direction: column; + + h2 { + text-align: center; + margin: 0; + background: linear-gradient(to bottom, #ffeeca, #efba99); + padding: 8px; + border-top: 1px solid #daaa55; + margin-top: 16px; + } + + item-titles { + display: grid; + background: linear-gradient(to bottom, #fefefe, #efefef); + padding: 8px; + gap: 4px; + border-top: 2px solid #eee; + border-bottom: 1px solid #eee; + } + item-first-titles { + grid-row: 1; + display: grid; + grid-auto-flow: column; + grid-auto-columns: 1fr auto; + align-items: baseline; + } + h3 { + margin: 0; + } + + item-network { + grid-row: 1; + grid-column: 2; + } + secondary-title { + font-weight: bold; + grid-row: 2; + grid-column: 1; + } + tertiary-title { + grid-row: 3; + grid-column: 1; + } + + module-description, synopsis { + white-space: pre-wrap; + margin: 8px; + display: block; + } + + details { + summary { + list-style: none; + &::after { + font-weight: bold; + content: ' More…'; + } + } + &[open] summary { + font-size: 0; + &::after { + font-size: 10pt; + content: 'Less…'; + } + } + } + + a { + display: block; + background: #efefef; + padding: 4px; + text-align: center; + font-weight: bold; + } +} + +inline-header-module { + display: block; + padding: 16px; + text-align: center; + module-items { + display: none; + } +} + +#listen_sign_in_upsell { +h2 { margin-top: 0; } +module-description::after { + display: block; + margin-top: 8px; + content: "Please note this is an unofficial frontend to BBC services and is not affiliated with or endorsed by the BBC in any way."; +} +} +inline-display-module:first-child h2 { margin-top: 0; } + +#listen_live { +overflow-x: scroll; + +module-description { + display: none; +} + +h2 { + position: sticky; + left: 0; +} + +playable-item { + display: block; + border-right: 1px solid #ccc; + width: 300px; + background: #eee; +} + +module-items { + display: flex; + flex-direction: row; + overflow-x: scroll; + border-bottom: 1px solid #ccc; + width: max-content; +} +} + +#single_item_promo { +margin: 32px; +border: 1px solid #963; +border-radius: 4px; +overflow: clip; +> module-items > display-item > item-titles { + background: #ffeecb; + border: none; +} +h2 { display: none; } +} + +#categories { +display: flex; + +container-item { + display: flex; + align-items: baseline; + background: #efefef; + border-top: 1px solid #fec; + border-bottom: 1px solid #aaa; + + item-titles { + background: none; + border: none; + } + + a { + font-size: 0; + &::after { + font-size: 10pt; + content: "view category »" + } + } +} +} + +segment-item { + display: grid; + grid-template-columns: 1fr auto auto; + align-items: baseline; + padding: 0 8px 0 0; + background: linear-gradient(to bottom, #fff, #f8f8f8); + border-bottom: 1px solid #ccc; + + item-titles { + background: none; + border: none; + } + + primary-title { + grid-row: 1; + grid-column: 1; + } + secondary-title { + grid-row: 2; + grid-column: 1; + } + tertiary-title { + white-space: pre-wrap; + font-size: 8pt; + } + offset-start { + grid-row: 1; + grid-column: 2; + &::after { + content: '—'; + } + } + offset-end { + grid-row: 1; + grid-column: 3; + } +} |
