new file: Fonts/Futura.ttf
new file: Fonts/Ubuntu.ttf modified: LICENSE modified: README.md new file: index.html new file: script.js new file: style.cssmain
parent
f077951cd9
commit
e8439e86ac
Binary file not shown.
Binary file not shown.
@ -1,10 +1,63 @@
|
||||
This is free and unencumbered software released into the public domain.
|
||||
COMMUNIST DIGITAL MANIFEST LICENSE (CDM) v1.0
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means.
|
||||
PREAMBLE
|
||||
Digital resources constitute collective human heritage. This license guarantees freedom for individual creators while ensuring collective accountability for commercial exploitation. Through these terms, humanity asserts sovereignty over its digital means of production.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law.
|
||||
ARTICLE 1: DEFINITIONS
|
||||
1.1 "The Commons" refers to the software and associated materials governed by this license.
|
||||
1.2 "Laborer" denotes any non-commercial user, including individuals and donation-funded projects.
|
||||
1.3 "Capital" signifies any entity generating revenue beyond voluntary donations through use of the Commons.
|
||||
1.4 "Derivative Work" means any modification, extension, or adaptation serving third parties.
|
||||
1.5 "Public Deployment" indicates any implementation accessible beyond the deployer's immediate control.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
ARTICLE 2: UNIVERSAL GRANTS
|
||||
Permission is hereby granted to all persons to use, reproduce, modify, and distribute the Commons without restriction, provided the subsequent conditions are met according to the nature of deployment.
|
||||
|
||||
For more information, please refer to <http://unlicense.org/>
|
||||
ARTICLE 3: CAPITAL OBLIGATIONS
|
||||
Capital deploying Derivative Works must satisfy these requirements:
|
||||
|
||||
3.1 Source Liberation:
|
||||
Complete corresponding source code for all Derivative Works must be made available, including build scripts, installation instructions, and modification records.
|
||||
|
||||
3.2 Commons Attribution:
|
||||
A visible acknowledgment must appear in user-accessible interfaces stating: "This product incorporates the [Commons Name] digital commons. Support community-owned technology at [Project URL]."
|
||||
|
||||
3.3 Data Sovereignty:
|
||||
Mechanisms for one-click export of user data must be implemented using open formats (JSON, CSV, or ActivityPub), enabling migration to compatible systems without functional degradation.
|
||||
|
||||
ARTICLE 4: PUBLIC DEPLOYMENT REQUIREMENTS
|
||||
All Public Deployments regardless of funding must comply with:
|
||||
|
||||
4.1 Asset Transparency:
|
||||
Editable source formats must accompany non-branding assets, including documentation (Markdown/ODT), graphics (SVG/PSD), and configurations (YAML/JSON).
|
||||
|
||||
4.2 Interoperability:
|
||||
Networked deployments must implement standardized federation protocols ensuring equal access across compatible instances.
|
||||
|
||||
ARTICLE 5: PROHIBITIONS
|
||||
|
||||
5.1 Trademark Protection:
|
||||
Use of project names or logos implying official affiliation is prohibited. Modified branding elements are permitted only when visually distinct and explicitly identified as unofficial.
|
||||
|
||||
5.2 Contributor Warranty:
|
||||
Contributors affirm they possess necessary rights for submitted materials. Infringement liability rests exclusively with the contributor.
|
||||
|
||||
ARTICLE 6: ENFORCEMENT
|
||||
|
||||
6.1 Violation Remedies:
|
||||
Capital failing attribution obligations forfeits license rights. Public deployments concealing assets must release source materials within thirty (30) days. Trademark violations warrant immediate takedown notices.
|
||||
|
||||
6.2 Cure Period:
|
||||
Violators retain license rights for thirty (30) days following formal notification of non-compliance.
|
||||
|
||||
ARTICLE 7: LABORER EXEMPTION
|
||||
Laborers are exempt from Articles 3-5. They may freely modify branding, disable features, and create private derivatives without obligation.
|
||||
|
||||
ARTICLE 8: LICENSE EVOLUTION
|
||||
Future versions require majority approval from active contributors. Existing deployments may operate under preceding terms.
|
||||
|
||||
ARTICLE 9: DISCLAIMERS
|
||||
THE COMMONS IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. CONTRIBUTORS SHALL NOT BE LIABLE FOR DAMAGES ARISING FROM ITS USE. THIS LICENSE DOES NOT GRANT TRADEMARK RIGHTS BEYOND ARTICLE 5.1.
|
||||
|
||||
IMPLEMENTATION
|
||||
This document constitutes the complete license terms. Retain this unmodified text as "LICENSE.md" in your repository root.
|
@ -1,3 +1,3 @@
|
||||
# Radio
|
||||
|
||||
The Frontend UI of the [ CiF ] Web-Radio
|
||||
This is the Code of the [ CiF ] Radio Web-UI. The Backend is powered by LibreTime and IceCast2.
|
@ -0,0 +1,55 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="https://code.cif.su/CiF/Branding/raw/branch/main/Logos/Radio/CiF-Radio.svg">
|
||||
<link rel="stylesheet" href="./style.css">
|
||||
<script src="./script.js"></script>
|
||||
<title>[ CiF ] Radio</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1 class="slidefadein title"><span class="no-break">[ CiF ] <img src="https://code.cif.su/CiF/Branding/raw/branch/main/Logos/Radio/CiF-Radio.svg"> Radio</span></h1>
|
||||
<h2 class="slidefadein"><span class="no-break">The Sound of Socialism</span></h2>
|
||||
</header>
|
||||
|
||||
<div class="card neomorphic">
|
||||
<div class="player">
|
||||
|
||||
<div class="child">
|
||||
<div id="cover-container">
|
||||
<div class="image-wrapper neomorphic">
|
||||
<img id="placeholder-image" alt="Placeholder image">
|
||||
<img id="cover-image" alt="Current track cover">
|
||||
<img id="temp-image" alt="Temporary cover">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="now-playing neomorphic-i">
|
||||
<div class="song-title" id="songTitle">Not Connected</div>
|
||||
<div class="artist" id="artist">[ CiF ] Radio</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="child2">
|
||||
<div class="volume-knob-container">
|
||||
<div class="knob-surround neomorphic">
|
||||
<div id="knob" class="knob"></div>
|
||||
<span class="min">Min</span>
|
||||
<span class="max">Max</span>
|
||||
<div id="tickContainer" class="ticks"></div>
|
||||
</div>
|
||||
<div class="volume-value">Volume: <span id="volumeValue">0%</span></div>
|
||||
</div>
|
||||
|
||||
<div class="stats">
|
||||
<p>You're listening together with <span id="listeners">--</span> Comrades</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,297 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const songTitle = document.getElementById('songTitle');
|
||||
const artist = document.getElementById('artist');
|
||||
const statusElement = document.getElementById('status');
|
||||
const listenersElement = document.getElementById('listeners');
|
||||
const bitrateElement = document.getElementById('bitrate');
|
||||
const uptimeElement = document.getElementById('uptime');
|
||||
|
||||
const audio = new Audio('https://broadcast.cif.su/main');
|
||||
audio.crossOrigin = 'anonymous';
|
||||
let isPlaying = false;
|
||||
let metadataInterval;
|
||||
let statsInterval;
|
||||
let currentShow = '';
|
||||
|
||||
// Volume Knob Variables
|
||||
var knobPositionX;
|
||||
var knobPositionY;
|
||||
var mouseX;
|
||||
var mouseY;
|
||||
var knobCenterX;
|
||||
var knobCenterY;
|
||||
var adjacentSide;
|
||||
var oppositeSide;
|
||||
var currentRadiansAngle;
|
||||
var getRadiansInDegrees;
|
||||
var finalAngleInDegrees;
|
||||
var volumeSetting = 0; // Set initial volume to 0%
|
||||
var tickHighlightPosition;
|
||||
var startingTickAngle = -135;
|
||||
var tickContainer = document.getElementById("tickContainer");
|
||||
var volumeKnob = document.getElementById("knob");
|
||||
var boundingRectangle = volumeKnob.getBoundingClientRect();
|
||||
|
||||
// Set initial volume
|
||||
audio.volume = volumeSetting / 100;
|
||||
|
||||
// Format time (seconds to HH:MM:SS)
|
||||
function formatTime(seconds) {
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
const minutes = Math.floor((seconds % 3600) / 60);
|
||||
const secs = Math.floor(seconds % 60);
|
||||
|
||||
return [
|
||||
hours.toString().padStart(2, '0'),
|
||||
minutes.toString().padStart(2, '0'),
|
||||
secs.toString().padStart(2, '0')
|
||||
].join(':');
|
||||
}
|
||||
|
||||
// Fetch Icecast stats
|
||||
async function fetchStats() {
|
||||
try {
|
||||
const response = await fetch('https://broadcast.cif.su/status-json.xsl');
|
||||
if (!response.ok) throw new Error('Network response was not ok');
|
||||
|
||||
const data = await response.json();
|
||||
const source = data.icestats.source;
|
||||
|
||||
if (source) {
|
||||
// Update stats
|
||||
listenersElement.textContent = source.listeners || '0';
|
||||
|
||||
if (source.server_start_iso8601) {
|
||||
const startTime = new Date(source.server_start_iso8601);
|
||||
const uptimeSeconds = Math.floor((new Date() - startTime) / 1000);
|
||||
uptimeElement.textContent = formatTime(uptimeSeconds);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching stats:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch Libretime current track info
|
||||
async function fetchCurrentTrack() {
|
||||
try {
|
||||
const response = await fetch('https://fm.cif.su/api/live-info');
|
||||
if (!response.ok) throw new Error('Network response was not ok');
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.current && data.current.metadata) {
|
||||
const track = data.current.metadata;
|
||||
|
||||
// Update song info
|
||||
songTitle.textContent = track.track_title || 'Unknown Track';
|
||||
artist.textContent = track.artist_name || 'Unknown Artist';
|
||||
|
||||
// Update show info if available
|
||||
if (data.current.show && data.current.show.name !== currentShow) {
|
||||
currentShow = data.current.show.name;
|
||||
statusElement.textContent = isPlaying ? 'Playing' : `Current Show: ${currentShow}`;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching current track:', error);
|
||||
// Fallback to Icecast metadata if Libretime API fails
|
||||
fetchIcecastMetadata();
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to Icecast metadata if needed
|
||||
async function fetchIcecastMetadata() {
|
||||
try {
|
||||
const response = await fetch('https://broadcast.cif.su/status-json.xsl');
|
||||
if (!response.ok) throw new Error('Network response was not ok');
|
||||
|
||||
const data = await response.json();
|
||||
const source = data.icestats.source;
|
||||
|
||||
if (source && source.title) {
|
||||
const titleParts = source.title.split(' - ');
|
||||
if (titleParts.length > 1) {
|
||||
artist.textContent = titleParts[0];
|
||||
songTitle.textContent = titleParts.slice(1).join(' - ');
|
||||
} else {
|
||||
songTitle.textContent = source.title;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching Icecast metadata:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Volume Knob Functions
|
||||
function initVolumeKnob() {
|
||||
// Calculate initial rotation angle based on initial volume (0%)
|
||||
const initialAngle = (volumeSetting / 100) * 270;
|
||||
volumeKnob.style.transform = "rotate(" + initialAngle + "deg)";
|
||||
|
||||
// Calculate how many ticks to highlight (0% of 27 ticks is 0)
|
||||
tickHighlightPosition = Math.round((volumeSetting * 2.7) / 10);
|
||||
createTicks(27, tickHighlightPosition);
|
||||
|
||||
volumeKnob.addEventListener(getMouseDown(), onMouseDown);
|
||||
document.addEventListener(getMouseUp(), onMouseUp);
|
||||
}
|
||||
|
||||
//on mouse button down
|
||||
function onMouseDown() {
|
||||
document.addEventListener(getMouseMove(), onMouseMove); //start drag
|
||||
}
|
||||
|
||||
//on mouse button release
|
||||
function onMouseUp() {
|
||||
document.removeEventListener(getMouseMove(), onMouseMove); //stop drag
|
||||
}
|
||||
|
||||
//compute mouse angle relative to center of volume knob
|
||||
function onMouseMove(event) {
|
||||
knobPositionX = boundingRectangle.left;
|
||||
knobPositionY = boundingRectangle.top;
|
||||
|
||||
if(detectMobile() == "desktop") {
|
||||
mouseX = event.pageX;
|
||||
mouseY = event.pageY;
|
||||
} else {
|
||||
mouseX = event.touches[0].pageX;
|
||||
mouseY = event.touches[0].pageY;
|
||||
}
|
||||
|
||||
knobCenterX = boundingRectangle.width / 2 + knobPositionX;
|
||||
knobCenterY = boundingRectangle.height / 2 + knobPositionY;
|
||||
|
||||
adjacentSide = knobCenterX - mouseX;
|
||||
oppositeSide = knobCenterY - mouseY;
|
||||
|
||||
currentRadiansAngle = Math.atan2(adjacentSide, oppositeSide);
|
||||
|
||||
getRadiansInDegrees = currentRadiansAngle * 180 / Math.PI;
|
||||
|
||||
finalAngleInDegrees = -(getRadiansInDegrees - 135);
|
||||
|
||||
if(finalAngleInDegrees >= 0 && finalAngleInDegrees <= 270) {
|
||||
volumeKnob.style.transform = "rotate(" + finalAngleInDegrees + "deg)";
|
||||
volumeSetting = Math.floor(finalAngleInDegrees / (270 / 100));
|
||||
tickHighlightPosition = Math.round((volumeSetting * 2.7) / 10);
|
||||
createTicks(27, tickHighlightPosition);
|
||||
|
||||
// Handle play/pause based on volume setting
|
||||
if (volumeSetting > 0 && !isPlaying) {
|
||||
audio.play()
|
||||
.then(() => {
|
||||
statusElement.textContent = 'Playing';
|
||||
isPlaying = true;
|
||||
// Don't clear the metadata interval when pausing
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error playing stream:', error);
|
||||
statusElement.textContent = 'Error: ' + error.message;
|
||||
});
|
||||
} else if (volumeSetting === 0 && isPlaying) {
|
||||
audio.pause();
|
||||
statusElement.textContent = currentShow ? `Current Show: ${currentShow}` : 'Paused';
|
||||
isPlaying = false;
|
||||
// Don't clear the metadata interval when pausing
|
||||
}
|
||||
|
||||
audio.volume = volumeSetting / 100;
|
||||
document.getElementById("volumeValue").textContent = volumeSetting + "%";
|
||||
}
|
||||
}
|
||||
|
||||
//dynamically create volume knob "ticks"
|
||||
function createTicks(numTicks, highlightNumTicks) {
|
||||
while(tickContainer.firstChild) {
|
||||
tickContainer.removeChild(tickContainer.firstChild);
|
||||
}
|
||||
|
||||
for(var i=0;i<numTicks;i++) {
|
||||
var tick = document.createElement("div");
|
||||
tick.className = i < highlightNumTicks ? "tick activetick" : "tick";
|
||||
tickContainer.appendChild(tick);
|
||||
tick.style.transform = "rotate(" + startingTickAngle + "deg)";
|
||||
startingTickAngle += 10;
|
||||
}
|
||||
|
||||
startingTickAngle = -135;
|
||||
}
|
||||
|
||||
//detect for mobile devices
|
||||
function detectMobile() {
|
||||
var result = (navigator.userAgent.match(/(iphone)|(ipod)|(ipad)|(android)|(blackberry)|(windows phone)|(symbian)/i));
|
||||
return result !== null ? "mobile" : "desktop";
|
||||
}
|
||||
|
||||
function getMouseDown() {
|
||||
return detectMobile() == "desktop" ? "mousedown" : "touchstart";
|
||||
}
|
||||
|
||||
function getMouseUp() {
|
||||
return detectMobile() == "desktop" ? "mouseup" : "touchend";
|
||||
}
|
||||
|
||||
function getMouseMove() {
|
||||
return detectMobile() == "desktop" ? "mousemove" : "touchmove";
|
||||
}
|
||||
|
||||
// Initial setup
|
||||
fetchStats();
|
||||
fetchCurrentTrack();
|
||||
statsInterval = setInterval(fetchStats, 10000);
|
||||
// Start metadata updates regardless of playback state
|
||||
metadataInterval = setInterval(fetchCurrentTrack, 10000);
|
||||
|
||||
initVolumeKnob();
|
||||
|
||||
audio.addEventListener('error', function() {
|
||||
statusElement.textContent = 'Error connecting to stream';
|
||||
isPlaying = false;
|
||||
});
|
||||
|
||||
// Cover art functionality
|
||||
const PLACEHOLDER_IMAGE_URL = 'https://code.cif.su/CiF/Branding/raw/branch/main/Logos/Radio/CiF-Radio.svg';
|
||||
let currentImageVisible = true;
|
||||
document.getElementById('placeholder-image').src = PLACEHOLDER_IMAGE_URL;
|
||||
|
||||
async function fetchNowPlaying() {
|
||||
try {
|
||||
const response = await fetch('https://fm.cif.su/api/live-info');
|
||||
const data = await response.json();
|
||||
const trackId = data.current.metadata.id;
|
||||
const artworkUrl = `https://fm.cif.su/api/track?id=${trackId}&return=artwork&t=${new Date().getTime()}`;
|
||||
|
||||
const visibleImage = currentImageVisible ? 'cover-image' : 'temp-image';
|
||||
const hiddenImage = currentImageVisible ? 'temp-image' : 'cover-image';
|
||||
const hiddenImg = document.getElementById(hiddenImage);
|
||||
const placeholderImg = document.getElementById('placeholder-image');
|
||||
|
||||
hiddenImg.onload = function() {
|
||||
placeholderImg.style.opacity = '0';
|
||||
hiddenImg.style.opacity = '1';
|
||||
document.getElementById(visibleImage).style.opacity = '0';
|
||||
currentImageVisible = !currentImageVisible;
|
||||
};
|
||||
|
||||
hiddenImg.onerror = function() {
|
||||
placeholderImg.style.opacity = '1';
|
||||
document.getElementById('cover-image').style.opacity = '0';
|
||||
document.getElementById('temp-image').style.opacity = '0';
|
||||
};
|
||||
|
||||
hiddenImg.src = artworkUrl;
|
||||
} catch (error) {
|
||||
console.error('Error fetching now playing data:', error);
|
||||
document.getElementById('placeholder-image').style.opacity = '1';
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('cover-image').style.opacity = '0';
|
||||
document.getElementById('temp-image').style.opacity = '0';
|
||||
document.getElementById('placeholder-image').style.opacity = '1';
|
||||
|
||||
fetchNowPlaying();
|
||||
setInterval(fetchNowPlaying, 10000);
|
||||
});
|
@ -0,0 +1,357 @@
|
||||
/*Fonts*/
|
||||
@font-face {
|
||||
font-family: "Futura";
|
||||
src: url("./Fonts/Futura.ttf") format("truetype";)
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Ubuntu";
|
||||
src: url("./Fonts/Ubuntu.ttf") format("truetype");
|
||||
}
|
||||
|
||||
|
||||
/*Knob CSS*/
|
||||
.knob-surround {
|
||||
position: relative;
|
||||
width: 250px;
|
||||
height: 250px;
|
||||
border-radius: 50%;
|
||||
margin: 1em auto;
|
||||
}
|
||||
.knob {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
-webkit-transform: rotate(0deg);
|
||||
-moz-transform: rotate(0deg);
|
||||
-o-transform: rotate(0deg);
|
||||
-ms-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
z-index: 10;
|
||||
}
|
||||
.knob:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 19%;
|
||||
left: 19%;
|
||||
width: 3%;
|
||||
height: 3%;
|
||||
background-color: #ff5a99;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.min,
|
||||
.max {
|
||||
display: block;
|
||||
font-family: "Ubuntu";
|
||||
color: #ff5a99;
|
||||
text-transform: uppercase;
|
||||
font-size: 75%;
|
||||
position: absolute;
|
||||
opacity: 0.50;
|
||||
}
|
||||
.min {
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
}
|
||||
.max {
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
}
|
||||
.tick {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 5;
|
||||
overflow: visible;
|
||||
}
|
||||
.tick:after {
|
||||
content: "";
|
||||
width: 0.08em;
|
||||
height: 0.6em;
|
||||
position: absolute;
|
||||
top: -20px;
|
||||
left: 50%;
|
||||
transition: all 1000ms ease-in-out;
|
||||
}
|
||||
.activetick:after {
|
||||
top: -25px;
|
||||
transition: all 1000ms ease-in-out;
|
||||
}
|
||||
|
||||
.volume-knob-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.volume-value {
|
||||
margin-top: 2.5px;
|
||||
font-family: "Ubuntu";
|
||||
font-size: 15px;
|
||||
color: #ff5a99;
|
||||
opacity: 0.50;
|
||||
}
|
||||
|
||||
/*Song Info*/
|
||||
.now-playing {
|
||||
text-align: center;
|
||||
margin-top: 2rem;
|
||||
padding: 1rem;
|
||||
border-radius: 30px;
|
||||
}
|
||||
|
||||
.song-title {
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.artist {
|
||||
color: #aaa;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/*Basic Site CSS*/
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
padding-top: 10px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
scroll-behavior: smooth;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
margin-top: 10px;
|
||||
font-family: "Ubuntu";
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 25pt;
|
||||
z-index: 1;
|
||||
margin-top: 5pt;
|
||||
}
|
||||
|
||||
@keyframes slideAndFade {
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.title{
|
||||
font-size: 120px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.card {
|
||||
width: 800pt;
|
||||
height: auto;
|
||||
border-radius: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
margin-top: 30pt;
|
||||
}
|
||||
|
||||
.cards1 {
|
||||
width: 180pt;
|
||||
height: auto;
|
||||
border-radius: 40pt;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column; /* Changed from column to row */
|
||||
margin-top: 30pt;
|
||||
gap: 20px; /* Adds some space between image and text */
|
||||
}
|
||||
|
||||
.cards2 {
|
||||
width: 150pt;
|
||||
height: auto;
|
||||
border-radius: 30pt;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/*Light Color Themes*/
|
||||
@media (prefers-color-scheme: light) {
|
||||
.title{
|
||||
color: #ff5a99;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #ff5a99;
|
||||
}
|
||||
|
||||
.neomorphic{
|
||||
background: #ffa7c7;
|
||||
box-shadow: -5px -5px 20px rgb(254,254,255, 0.69),
|
||||
2px 2px 20px rgb(255,90,153)
|
||||
}
|
||||
|
||||
.neomorphic-i{
|
||||
background: #ffa7c7;
|
||||
box-shadow: -3px -3px 15px rgba(254, 254, 255, 0.69) inset,
|
||||
1px 1px 15px rgb(255,90,153) inset
|
||||
}
|
||||
|
||||
.neomorphic2{
|
||||
background: #ffa7c7;
|
||||
transition: all 0.1s ease-in-out;
|
||||
transform: scale(1);
|
||||
cursor: pointer;
|
||||
box-shadow: -5px -5px 20px rgba(254,254,255,0.5),
|
||||
2px 2px 20px rgb(255,90,153)
|
||||
}
|
||||
|
||||
.neomorphic2:hover{
|
||||
background: #ffa7c7;
|
||||
transform: scale(0.98);
|
||||
box-shadow: -5px -5px 20px rgba(254, 254, 255, 0),
|
||||
2px 2px 20px rgba(255, 90, 153, 0)
|
||||
}
|
||||
|
||||
.neomorphic2:active{
|
||||
background: #ffa7c7;
|
||||
transform: scale(0.96);
|
||||
box-shadow: -3px -3px 15px rgba(254, 254, 255, 0.69) inset,
|
||||
1px 1px 15px rgb(255,90,153) inset
|
||||
}
|
||||
|
||||
.cards2 h2 {
|
||||
color: #060606;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.tick:after {
|
||||
background-color: #ff5a9980;
|
||||
}
|
||||
.activetick:after {
|
||||
background-color: #fff3f9;
|
||||
box-shadow: 0 0 0.3em 0.08em #fff3f9;
|
||||
}
|
||||
}
|
||||
|
||||
/*Dark Color Themes*/
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root{
|
||||
background-color: #060606;
|
||||
}
|
||||
|
||||
.title{
|
||||
color: #00d791;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #55052c;
|
||||
}
|
||||
|
||||
.neomorphic{
|
||||
background: #55052c;
|
||||
box-shadow: -5px -5px 20px rgba(1, 1, 0, 0.69),
|
||||
2px 2px 20px rgba(0, 215, 143, 0.425)
|
||||
}
|
||||
|
||||
.neomorphic-i{
|
||||
background: #55052c;
|
||||
box-shadow: -3px -3px 15px rgba(1, 1, 0, 0.69) inset,
|
||||
1px 1px 15px rgb(0, 0, 0) inset
|
||||
}
|
||||
|
||||
.neomorphic2{
|
||||
background: #55052c;
|
||||
box-shadow: -5px -5px 20px rgba(1, 1, 0, 0.5),
|
||||
2px 2px 20px rgba(0, 215, 143, 0.534)
|
||||
}
|
||||
|
||||
.tick:after {
|
||||
background-color: #06060680;
|
||||
}
|
||||
|
||||
.activetick:after {
|
||||
background-color: #00d791;;
|
||||
box-shadow: 0 0 0.3em 0.08em #00d791;
|
||||
}
|
||||
}
|
||||
|
||||
header img {
|
||||
height: 120px;
|
||||
width: auto;
|
||||
align-content: baseline;
|
||||
}
|
||||
|
||||
#cover-container {
|
||||
margin: 20px auto;
|
||||
max-width: 400px;
|
||||
position: relative;
|
||||
}
|
||||
.image-wrapper {
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
border-radius: 30px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background-color: #e0e0e000;
|
||||
}
|
||||
#cover-image, #temp-image, #placeholder-image {
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
object-fit: cover;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
#placeholder-image {
|
||||
width: 350px;
|
||||
height: 350px;
|
||||
object-fit: cover;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
#temp-image {
|
||||
opacity: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
#cover-image {
|
||||
z-index: 2;
|
||||
}
|
||||
#placeholder-image {
|
||||
z-index: 3;
|
||||
opacity: 1;
|
||||
margin: 25px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.container {
|
||||
text-align: center;
|
||||
}
|
||||
.child {
|
||||
display: inline-block;
|
||||
padding: 1rem 1rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.child2 {
|
||||
z-index: -20;
|
||||
display: inline-block;
|
||||
padding: 1rem 1rem;
|
||||
vertical-align: middle;
|
||||
}
|
Loading…
Reference in New Issue