Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <div class="app-header">
	<h1>Ride Sort</h1>
</div>

<main class="content-wrapper" x-data="app">

<p>
Select which park you are in: 
<select x-model="selectedPark">
	<option value="">-- Select a park --</option>
	<template x-for="park in parks" :key="park.id">
	<option x-text="park.name" :value="park.id"></option>
	</template>
</select> <button class="btn" x-show="parksLoaded" @click="loadTimes">Get Times</button>
<div class="checkbox-control">
    <input type="checkbox" x-model="showClosed" id="showClosedCheckbox" class="themed-checkbox">
    <label for="showClosedCheckbox" class="checkbox-label">Show Closed Rides (<span x-text="allRides.length"></span> total rides)</label>
</div>
</p>

<div class="loader" style="display:none" x-show="loadingRides" id="loader"></div>

<template x-if="ridesLoaded">
	<div class="ride-list">
	<template x-for="ride in rides">

		<div class="ride-card">
			<h2 x-text="ride.name"></h2>
			<p class="wait-time"><span x-text="ride.wait_time"></span> <span class="unit">minutes</span></p>
			<!-- is_open: true or false -->
			<p class="info-item"><strong>Land:</strong> <span x-text="ride.land"></span></p>
			<span class="status" :class="ride.is_open ? 'open':'closed'" x-text="ride.is_open ? 'Open':'Closed'"></span></span>
		</div>
	</template>
	</div> 
</template>

<script src="//unpkg.com/alpinejs" defer></script>



<footer class="app-footer">
<p>
Created by <a href="https://www.raymondcamden.com" target="_blank">Ramond Camden</a> ~ <a href="https://queue-times.com/en-US" target="_blank">Powered by Queue-Times.com</a>
</p>
</footer>
              
            
!

CSS

              
                @import url('https://fonts.googleapis.com/css2?family=Luckiest+Guy&family=Poppins:wght@400;600&display=swap');

/* Global Resets & Base Styles */
* {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

html {
    font-size: 16px; /* Base font size */
}

body {
    font-family: 'Poppins', sans-serif;
    line-height: 1.6;
    background-color: #FDF0D5; /* Light, warm parchment - like an old park map */
    color: #2D3748; /* Dark grayish blue for text - good contrast */
    padding: 1rem; /* Padding for small screens */
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

/* App Header */
.app-header {
    background: linear-gradient(135deg, #E63946, #F4A261); /* Fiery Rose to Sandy Brown gradient - fun & energetic! */
    color: white;
    padding: 2rem 1rem;
    text-align: center;
    /* Extend to screen edges on mobile by undoing body padding */
    margin: -1rem -1rem 2rem -1rem;
    border-bottom-left-radius: 30px;
    border-bottom-right-radius: 30px;
    box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
}

.app-header h1 {
    font-family: 'Luckiest Guy', cursive; /* Very playful font! */
    font-size: 2.8rem; /* Base size, will adjust for larger screens */
    margin: 0;
    letter-spacing: 1px;
    text-shadow: 3px 3px 0px rgba(0,0,0,0.1); /* Subtle 3D effect */
}

/* Main Content Wrapper - for centering content on larger screens */
.content-wrapper {
    max-width: 1200px;
    margin: 0 auto;
    padding: 0 1rem; /* Horizontal padding for content inside wrapper */
}

/* Ride List Container */
.ride-list {
	margin-top: 20px;
    display: grid;
    gap: 1.5rem;
    /* Mobile: single column by default. Grid handles responsiveness below. */
}

/* Ride Card Styling */
.ride-card {
    background-color: #FFFFFF;
    border-radius: 12px;
    padding: 1.5rem;
    box-shadow: 0 4px 8px rgba(45, 55, 72, 0.1);
    transition: transform 0.3s ease, box-shadow 0.3s ease;
    border-left: 8px solid #F4A261; /* Sandy orange accent - ties to header */
    overflow: hidden; /* Ensures content respects border-radius */
}

.ride-card:hover {
    transform: translateY(-5px) scale(1.02); /* Fun pop effect on hover */
    box-shadow: 0 8px 16px rgba(45, 55, 72, 0.15);
}

.ride-card h2 { /* Ride Name */
    font-family: 'Luckiest Guy', cursive;
    color: #264653; /* Dark Slate Gray/Tealish - strong and thematic */
    font-size: 1.6rem;
    margin-bottom: 0.5rem;
    letter-spacing: 0.5px;
}

.ride-card .info-item {
    margin-bottom: 0.75rem;
    font-size: 1rem;
}

.ride-card .info-item strong {
    color: #2D3748; /* Main text color */
    margin-right: 0.5em;
}

.ride-card .wait-time {
    font-size: 1.8rem;
    font-weight: 600; /* Poppins bold */
    color: #E63946; /* Fiery Rose - for high importance */
    display: block;
    margin-bottom: 0.5rem;
}

.ride-card .wait-time .unit {
    font-size: 1rem;
    font-weight: 400; /* Poppins regular */
    color: #718096; /* Lighter gray for "minutes" */
    margin-left: 0.25em;
}

.ride-card .status {
    font-weight: 600;
    padding: 0.25rem 0.5rem;
    border-radius: 6px;
    display: inline-block;
    font-size: 0.9rem;
    margin-top: 0.5rem;
}

.ride-card .status.open {
    background-color: #E6FFFA; /* Light Persian Green background */
    color: #2A9D8F; /* Persian Green text */
}

.ride-card .status.closed {
    background-color: #FFF5F5; /* Light red background */
    color: #E53E3E; /* Standard Red text */
}

/* Buttons (e.g., for refresh or filters) */
.btn {
    display: inline-block;
    background-color: #264653; /* Dark Slate Gray/Tealish */
    color: white;
    padding: 0.75rem 1.5rem;
    border: none;
    border-radius: 50px; /* Fun pill shape */
    font-family: 'Poppins', sans-serif;
    font-weight: 600;
    font-size: 1rem;
    text-decoration: none;
    text-align: center;
    cursor: pointer;
    transition: background-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

.btn:hover {
    background-color: #2A9D8F; /* Lighter Persian Green on hover */
    transform: translateY(-2px);
    box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}

.btn:active {
    transform: translateY(0);
    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

/* Message for when no rides are found or data is loading */
.info-message {
    text-align: center;
    padding: 2rem;
    font-size: 1.2rem;
    color: #718096; /* Lighter gray */
    background-color: #FFF;
    border-radius: 12px;
    box-shadow: 0 4px 8px rgba(45, 55, 72, 0.1);
}

/* Simple Loader/Spinner */
.loader {
    border: 8px solid #f0f0f0; /* Light grey */
    border-top: 8px solid #264653; /* Dark Slate Gray/Tealish - matches button */
    border-radius: 50%;
    width: 60px;
    height: 60px;
    animation: spin 1s linear infinite;
    margin: 3rem auto; /* Centered with some space */
}

@keyframes spin {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
}

/* Footer */
.app-footer {
    text-align: center;
    padding: 2rem 1rem;
    margin-top: 3rem;
    color: #718096; /* Lighter gray */
    font-size: 0.9rem;
    border-top: 1px solid #E2E8F0; /* Light gray border */
}

.app-footer p {
    margin: 0.25rem 0;
}

.app-footer a {
    color: #264653; /* Dark Slate Gray/Tealish */
    text-decoration: none;
    font-weight: 600;
}

.app-footer a:hover {
    text-decoration: underline;
    color: #E63946; /* Fiery Rose on hover */
}

/* Responsive Adjustments */

/* Tablet and small desktop (600px and up) */
@media (min-width: 600px) {
    body {
        padding: 0; /* Remove body padding, content-wrapper handles it */
    }

    .app-header {
        margin: 0 0 2.5rem 0; /* Adjust margin as body padding is removed */
        border-radius: 0; /* Full width header */
        padding: 2.5rem 1rem;
    }

    .app-header h1 {
        font-size: 3.5rem;
    }

    .ride-list {
        /* Creates responsive columns: fills space, each at least 280px wide */
        grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
        gap: 2rem;
    }

    .ride-card h2 {
        font-size: 1.8rem;
    }
}

select {
    font-family: 'Poppins', sans-serif;
    font-size: 1rem; /* Matches button font size */
    color: #2D3748; /* Dark grayish blue for text */
    background-color: #FFFFFF; /* White background, like cards */
    padding: 0.65rem 2.5rem 0.65rem 1rem; /* Top/bottom, Right (for arrow), Left */
    border: 2px solid #F4A261; /* Sandy Brown border - matches header accent & ride card */
    border-radius: 8px; /* Rounded corners, less than pill button, more like cards */
    margin-right: 0.5rem; /* Space between select and button */
    cursor: pointer;
    appearance: none; /* Remove default browser styling (especially arrow) */
    -webkit-appearance: none; /* For Safari/Chrome */
    -moz-appearance: none; /* For Firefox */
    /* Custom arrow using an inline SVG, colored to match dark elements */
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23264653'%3E%3Cpath d='M8 11.293l-4.646-4.647a.5.5 0 0 1 .708-.708L8 9.879l4.939-4.94a.5.5 0 0 1 .707.708L8 11.293z'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.75rem center; /* Position arrow on the right */
    background-size: 1em; /* Size of the arrow */
    min-width: 200px; /* Ensures it's not too squished, adjust as needed */
    vertical-align: middle; /* Helps align with adjacent button */
    transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

select:hover {
    border-color: #E63946; /* Fiery Rose on hover - matches header accent */
}

select:focus {
    outline: none; /* Remove default focus outline */
    border-color: #264653; /* Dark Slate Gray/Tealish on focus - matches button */
    box-shadow: 0 0 0 3px rgba(38, 70, 83, 0.25); /* Subtle focus ring, themed */
}

/* Basic styling for options (browser support for full styling is limited) */
select option {
    background-color: #FFFFFF;
    color: #2D3748;
}

/* Checkbox Control Styling */
.checkbox-control {
    margin-top: 1rem; /* Space above the checkbox group */
    display: flex; /* Align checkbox and label nicely */
    align-items: center;
    justify-content: flex-start; /* Or 'center' if you prefer it centered below controls */
}

input[type="checkbox"].themed-checkbox {
    appearance: none; /* Optional: if you want to build a fully custom one later */
    -webkit-appearance: none;
    -moz-appearance: none;
    width: 1.5em; /* Make it a bit larger and easier to tap */
    height: 1.5em;
    border: 2px solid #F4A261; /* Sandy Brown border, like select */
    border-radius: 4px; /* Slightly rounded corners */
    margin-right: 0.5em; /* Space between checkbox and label */
    cursor: pointer;
    vertical-align: middle;
    position: relative; /* For custom checkmark if not using accent-color */
    background-color: #fff; /* Background for unchecked state */
    accent-color: #2A9D8F; /* Persian Green - matches button hover, for the checkmark */
}

input[type="checkbox"].themed-checkbox:checked {
    background-color: #2A9D8F; /* Fill color when checked */
    border-color: #2A9D8F;
}

/* Custom checkmark for browsers that don't fully support accent-color on background or for more control */
input[type="checkbox"].themed-checkbox:checked::before {
    content: '✔'; /* Checkmark character */
    display: block;
    width: 100%;
    height: 100%;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    font-size: 1em; /* Adjust size of checkmark */
    color: white; /* Color of the checkmark */
    text-align: center;
    line-height: 1.5em; /* Align checkmark vertically */
}

.checkbox-label {
    font-family: 'Poppins', sans-serif;
    color: #2D3748; /* Dark grayish blue for text */
    font-size: 0.95rem;
    cursor: pointer;
    user-select: none; /* Prevents text selection when clicking label */
}


/* Larger desktops (992px and up) */
@media (min-width: 992px) {
    .app-header h1 {
        font-size: 4rem;
    }

    .ride-card {
        padding: 2rem; /* More padding on larger cards */
    }
}
              
            
!

JS

              
                const PARK_PROXY = 'https://raymondcamden--634fa2d2310a11f09b31569c3dd06744.web.val.run/';
const RIDE_PROXY = 'https://raymondcamden--12f63956310d11f0a367569c3dd06744.web.val.run/';

document.addEventListener('alpine:init', () => {
  Alpine.data('app', () => ({
	parks: [],
	allRides:[],
	selectedPark:null,
	showClosed:false, 
	loadingRides:false, 
	parksLoaded() {
		return this.parks.length > 0;
	},
	ridesLoaded() {
		return this.rides.length > 0;
	},
	async init() {
		this.parks = await getParks();
	},
	async loadTimes() {
		this.allRides = [];
		this.loadingRides = true;
		console.log(`get for ${this.selectedPark}`);
		this.allRides = await getRides(this.selectedPark);
		this.loadingRides = false;
	}, 
	get rides() {
		if(this.showClosed) return this.allRides;
		else return this.allRides.filter(r => r.is_open === true);
	}
  }))

});

async function getParks() {

	let res = await fetch(PARK_PROXY);
	let data = await res.json();
	let parks = [];
	/*
	structure is:
		array of companies
			array of parsk
	*/
	data.forEach(c => {
		c.parks.forEach(p => {
			parks.push(p);
		});
	});

	parks.sort((a,b) => {
		return a.name.localeCompare(b.name);
	});

	return parks;
}

async function getRides(id) {
	let res = await fetch(`${RIDE_PROXY}?id=${id}`);
	let data = await res.json();
	let rides = [];
	/*
	structure is:
		array of lands
			array of rides
	*/
	data.lands.forEach(l => {
		l.rides.forEach(r => {
			r.land = l.name;
			rides.push(r);
		});
	});

	rides.sort((a,b) => {
		return a.wait_time - b.wait_time;
	});

	return rides;

}
              
            
!
999px

Console