Add public domain day banner

This commit is contained in:
Alex Cabal 2024-11-27 12:42:18 -06:00
parent 1b604ca97f
commit c65035630f
16 changed files with 293 additions and 105 deletions

View file

@ -1,9 +1,10 @@
<?
// Auto-included by Composer in composer.json to satisfy PHPStan.
// Auto-included by Composer in `composer.json` to satisfy PHPStan.
use Safe\DateTimeImmutable;
use function Safe\define;
const NOW = new DateTimeImmutable();
const LATEST_CONTINENTAL_US_TZ = new DateTimeZone('America/Juneau');
const SITE_STATUS_LIVE = 'live';
const SITE_STATUS_DEV = 'dev';
@ -76,7 +77,7 @@ const ZOHO_WEBHOOK_LOG_FILE_PATH = '/var/log/local/webhooks-zoho.log'; // Must b
const DONATIONS_LOG_FILE_PATH = '/var/log/local/donations.log'; // Must be writable by `www-data` Unix user.
const ARTWORK_UPLOADS_LOG_FILE_PATH = '/var/log/local/artwork-uploads.log'; // Must be writable by `www-data` Unix user.
define('PD_YEAR', intval((new DateTimeImmutable('now', new DateTimeZone('America/Juneau')))->format('Y')) - 96); // Latest continental US time zone.
define('PD_YEAR', intval((new DateTimeImmutable('now', LATEST_CONTINENTAL_US_TZ))->format('Y')) - 96);
define('PD_STRING', 'January 1, ' . (PD_YEAR + 1));
// Controls the progress bar donation dialog.
@ -100,8 +101,8 @@ const DONATION_DRIVE_DATES = [
// Controls the countdown donation dialog, basically unused right now.
const DONATION_DRIVE_COUNTER_ENABLED = false;
const DONATION_DRIVE_COUNTER_START = new DateTimeImmutable('May 2, 2022 00:00:00 America/New_York');
const DONATION_DRIVE_COUNTER_END = new DateTimeImmutable('May 8, 2022 23:59:00 America/New_York');
const DONATION_DRIVE_COUNTER_START = new DateTimeImmutable('May 2, 2022 00:00:00', new DateTimeZone('America/New_York'));
const DONATION_DRIVE_COUNTER_END = new DateTimeImmutable('May 8, 2022 23:59:00', new DateTimeZone('America/New_York'));
const PD_DAY_YEAR = 2025;
const PD_DAY_DRAFT_PATH = '/standardebooks.org/drafts/' . PD_DAY_YEAR;

View file

@ -0,0 +1,8 @@
<?
namespace Enums;
enum ColorSchemeType: string{
case Auto = 'auto';
case Dark = 'dark';
case Light = 'light';
}

View file

@ -1,6 +1,6 @@
<?
// Hide the alert if the user has closed it.
if(!DONATION_DRIVE_COUNTER_ENABLED || ($autoHide ?? $_COOKIE['hide-donation-alert'] ?? false) || NOW > DONATION_DRIVE_COUNTER_END){
if(!DONATION_DRIVE_COUNTER_ENABLED || ($autoHide ?? (HttpInput::Bool(COOKIE, 'hide-donation-alert') ?? false)) || NOW > DONATION_DRIVE_COUNTER_END){
return;
}
@ -42,6 +42,7 @@ $digits = str_split(str_pad((string)$current, 3, "0", STR_PAD_LEFT))
<aside class="donation counter closable">
<? if($autoHide){ ?>
<form action="/settings" method="<?= Enums\HttpMethod::Post->value ?>">
<input type="hidden" name="_method" value="<?= Enums\HttpMethod::Patch->value ?>" />
<input type="hidden" name="hide-donation-alert" value="true" />
<button class="close" title="Close this box">Close this box</button>
</form>

View file

@ -4,7 +4,7 @@ $donationDrive = DonationDrive::GetByIsRunning();
if(
!DONATION_DRIVES_ENABLED // Drives aren't enabled.
||
($autoHide ?? $_COOKIE['hide-donation-alert'] ?? false) // If the user has hidden the box.
($autoHide ?? (HttpInput::Bool(COOKIE, 'hide-donation-alert') ?? false)) // If the user has hidden the box.
||
Session::$User !== null // If a user is logged in.
||
@ -44,6 +44,7 @@ else{
<aside class="donation closable">
<? if($autoHide){ ?>
<form action="/settings" method="<?= Enums\HttpMethod::Post->value ?>">
<input type="hidden" name="_method" value="<?= Enums\HttpMethod::Patch->value ?>" />
<input type="hidden" name="hide-donation-alert" value="true" />
<button class="close" title="Close this box">Close this box</button>
</form>

View file

@ -1,11 +1,12 @@
<?
use Safe\DateTimeImmutable;
use function Safe\filemtime;
$title = $title ?? '';
$highlight = $highlight ?? '';
$description = $description ?? '';
$manual = $manual ?? false;
$colorScheme = $_COOKIE['color-scheme'] ?? 'auto';
$colorScheme = Enums\ColorSchemeType::tryFrom(HttpInput::Str(COOKIE, 'color-scheme') ?? Enums\ColorSchemeType::Auto->value);
$isXslt = $isXslt ?? false;
$feedUrl = $feedUrl ?? null;
$feedTitle = $feedTitle ?? '';
@ -13,6 +14,7 @@ $isErrorPage = $isErrorPage ?? false;
$downloadUrl = $downloadUrl ?? null;
$canonicalUrl = $canonicalUrl ?? null;
$css = $css ?? [];
$showPublicDomainDayBanner = new DateTimeImmutable('now', LATEST_CONTINENTAL_US_TZ) < new DateTimeImmutable('January 14', LATEST_CONTINENTAL_US_TZ) && !(HttpInput::Bool(COOKIE, 'hide-public-domain-day-banner') ?? false);
// As of Sep. 2022, all versions of Safari have a bug where if the page is served as XHTML, then `<picture>` elements download all `<source>`s instead of the first supported match.
// So, we try to detect Safari here, and don't use multiple `<source>` if we find Safari.
@ -45,14 +47,14 @@ if(!$isXslt){
<link href="/css/ereader.css?version=<?= filemtime(WEB_ROOT . '/css/ereader.css') ?>" media="screen" rel="stylesheet" type="text/css"/>
<? }else{ ?>
<link href="/css/core.css?version=<?= filemtime(WEB_ROOT . '/css/core.css') ?>" media="screen" rel="stylesheet" type="text/css"/>
<? if($colorScheme == 'auto' || $colorScheme == 'dark'){ ?>
<link href="/css/dark.css?version=<?= filemtime(WEB_ROOT . '/css/dark.css') ?>" media="screen<? if($colorScheme == 'auto'){ ?> and (prefers-color-scheme: dark)<? } ?>" rel="stylesheet" type="text/css"/>
<? if($colorScheme == Enums\ColorSchemeType::Auto || $colorScheme == Enums\ColorSchemeType::Dark){ ?>
<link href="/css/dark.css?version=<?= filemtime(WEB_ROOT . '/css/dark.css') ?>" media="screen<? if($colorScheme == Enums\ColorSchemeType::Auto){ ?> and (prefers-color-scheme: dark)<? } ?>" rel="stylesheet" type="text/css"/>
<? } ?>
<? } ?>
<? if($manual){ ?>
<link href="/css/manual.css?version=<?= filemtime(WEB_ROOT . '/css/manual.css') ?>" media="screen" rel="stylesheet" type="text/css"/>
<? if($colorScheme == 'auto' || $colorScheme == 'dark'){ ?>
<link href="/css/manual-dark.css?version=<?= filemtime(WEB_ROOT . '/css/manual-dark.css') ?>" media="screen<? if($colorScheme == 'auto'){ ?> and (prefers-color-scheme: dark)<? } ?>" rel="stylesheet" type="text/css"/>
<? if($colorScheme == Enums\ColorSchemeType::Auto || $colorScheme == Enums\ColorSchemeType::Dark){ ?>
<link href="/css/manual-dark.css?version=<?= filemtime(WEB_ROOT . '/css/manual-dark.css') ?>" media="screen<? if($colorScheme == Enums\ColorSchemeType::Auto){ ?> and (prefers-color-scheme: dark)<? } ?>" rel="stylesheet" type="text/css"/>
<? } ?>
<? } ?>
<? foreach($css as $url){ ?>
@ -95,6 +97,27 @@ if(!$isXslt){
</head>
<body>
<header>
<? if($showPublicDomainDayBanner){ ?>
<div class="public-domain-day-banner">
<div id="confettis">
<div class="confetti"></div>
<div class="confetti"></div>
<div class="confetti"></div>
<div class="confetti"></div>
<div class="confetti"></div>
<div class="confetti"></div>
<div class="confetti"></div>
<div class="confetti"></div>
<div class="confetti"></div>
</div>
<strong>Happy Public Domain Day <?= PD_DAY_YEAR ?>!</strong> <a href="/blog/public-domain-day-<?= PD_DAY_YEAR ?>">See what new literature is free to read starting January 1.</a>
<form action="/settings" method="<?= Enums\HttpMethod::Post->value ?>">
<input type="hidden" name="_method" value="<?= Enums\HttpMethod::Patch->value ?>" />
<input type="hidden" name="hide-public-domain-day-banner" value="true" />
<button class="close" title="Hide this banner">Hide this banner</button>
</form>
</div>
<? } ?>
<a href="/">Standard Ebooks</a>
<nav>
<ul>

View file

@ -26,99 +26,86 @@ $ebooksWithDescriptions = [];
foreach($ebooks as $ebook){
$description = '';
$order = 0;
switch($ebook->Identifier){
case 'url:https://standardebooks.org/ebooks/william-faulkner/the-sound-and-the-fury':
$description = '<p>Faulkners widely-acclaimed masterpiece is well-known in America, as its taught in high schools across the country. In it we follow the Compson family, an aristocratic family in Mississippi, and their slow decline into poverty and ruin. What makes the novel so special—and what lends it its reputation as a challenging read—is its stream-of-consciousness style, in which Faulkner attempts to narrate the characters thoughts directly to the reader.</p><p><i>The Sound and the Fury</i> was an essential step in developing that modernist prose style, and is still considered to be one of the greatest works of American literature.</p>';
$order = 0;
break;
case 'url:https://standardebooks.org/ebooks/erich-maria-remarque/all-quiet-on-the-western-front/a-w-wheen':
$description = '<p>One of the greatest war novels to be born from any conflict, Erich Maria Remarques grisly tale of the brutality and horror of the German trenches during the Great War was so powerful that it earned him nominations for the Nobel Prizes in <em>both</em> Literature and Peace, and was soon widely banned in a Europe that was preparing for a second cataclysmic conflict.</p>';
$order = 1;
$description = '<p>One of the greatest war novels to come from any conflict, Erich Maria Remarques grisly tale of the brutality and horror of the German trenches during the Great War was so powerful that it earned him nominations for the Nobel Prizes in <em>both</em> Literature and Peace, and was soon widely banned in a Europe that was preparing for a second cataclysmic conflict.</p>';
break;
case 'url:https://standardebooks.org/ebooks/ernest-hemingway/a-farewell-to-arms':
$description = '<p>Called “the premier American war novel from World War I,” Hemingways semi-autobiographical story of an American ambulance driver serving in the Italian front, and a bright and cynical British nurse, cemented his reputation as one of the generations foremost literary figures. Unlike Remarque, whose <i>All Quite on the Western Front</i> paints the wars destruction in full color, Hemingways story is one of the mundanity of war—of the quotidien smallness that underpins even the most horrific of events.</p>';
$order = 2;
$description = '<p>Called “the premier American war novel from World War I,” Hemingways semi-autobiographical story of an American ambulance driver serving in the Italian front, and a bright and cynical British nurse, cemented his reputation as one of the generations foremost literary figures. Unlike Remarque, whose <i>All Quite on the Western Front</i> paints the wars destruction in full color, Hemingways story is one of the mundanity of war—of the quotidian smallness that underpins even the most horrific of events.</p>';
break;
case 'url:https://standardebooks.org/ebooks/john-steinbeck/cup-of-gold':
$description = '<p><i>Cup of Gold</i> is John Steinbecks first novel, the swashbuckling story of the real-life Captain Morgan, legendary pirate. Little of Morgans actual life is known, but Steinbeck fills in the blanks of this rich historical fiction with rich portraits of Caribbean ports and flourishes of magical realism.</p><p>While one might expect a rollicking tale of adventure, the novel is actually a deep meditation on the pursuit of—and inability to find—true happiness, and its skillful craft deftly foreshadows Steinbecks later ascenion to literary titanhood.</p>';
$order = 3;
$description = '<p><i>Cup of Gold</i> is John Steinbecks first novel, the swashbuckling story of the real-life Captain Morgan, legendary pirate. Little of Morgans actual life is known, but Steinbeck fills in the blanks of this rich historical fiction with rich portraits of Caribbean ports and flourishes of magical realism.</p><p>While one might expect a rollicking tale of adventure, the novel is actually a deep meditation on the pursuit of—and inability to find—true happiness, and its skillful craft deftly foreshadows Steinbecks later ascension to literary titanhood.</p>';
break;
case 'url:https://standardebooks.org/ebooks/dashiell-hammett/red-harvest':
$description = '<p><i>Red Harvest</i> is Dashiell Hammets first full-length novel to feature the <a href="/collections/continental-op">Continental Op</a>, the nameless, hard-drinking, cynical private eye that single-handedly created the archetype of the hard-boiled detective. The novel is a fast-paced, tightly-written murder thriller that simultaneously touches all the bases of, and <em>defines</em>, the classic noir style that was much-imitated, and later much-spoofed, in the following century.</p>';
$order = 4;
break;
case 'url:https://standardebooks.org/ebooks/sinclair-lewis/dodsworth':
$description = '<p>Samuel Dodsworth is a successful automobile executive who decides to retire early. His younger wife Fran wants to tour Europe, so the two embark on a trip. But soon after they arrive, Fran becomes enchanted by the whirlwind of culture and old-world society, while Samuel, a mild-mannered, down-to-earth Midwesterner, yearns to escape the pretentiousness and return to the quiet stability of home. The novel explores the slow breakdown of their marriage while deftly satirizing American midde-class mores and the wide gulf between them and European culture.</p>';
$order = 5;
$description = '<p>Samuel Dodsworth is a successful automobile executive who decides to retire early. His younger wife Fran wants to tour Europe, so the two embark on a trip. But soon after they arrive, Fran becomes enchanted by the whirlwind of culture and old-world society, while Samuel, a mild-mannered, down-to-earth Midwesterner, yearns to escape the pretentiousness and return to the quiet stability of home. The novel explores the slow breakdown of their marriage while deftly satirizing American middle-class mores and the wide gulf between them and European culture.</p>';
break;
case 'url:https://standardebooks.org/ebooks/oliver-la-farge/laughing-boy':
$description = '<p><i>Laughing Boy</i> is the story of the titular main character, a young Navajo man living in the American Southwest around the turn of the 20th century. He meets the fiery young Slim Girl at a tribal meet, but her reputation precedes her, and the tribe disapproves of their union. Ignoring the advice of the tribe, the two start a life together as they try to keep ancient traditions alive in the face of the rapidly-encroaching modernization of the American Southwest.</p><p><i>Laughing Boy</i> was the recipient of the 1930 <a href="/collections/pulitzer-prize-for-fiction-winners">Pulitzer Prize for Fiction</a>.</p>';
$order = 6;
$description = '<p><i>Laughing Boy</i>, the winner of the 1930 <a href="/collections/pulitzer-prize-for-fiction-winners">Pulitzer Prize for Fiction</a>, is the story of the titular main character, a young Navajo man living in the American Southwest around the turn of the 20th century. He meets the fiery young Slim Girl at a tribal meet, but her reputation precedes her, and the tribe disapproves of their union. Ignoring the advice of the tribe, the two start a life together as they try to keep ancient traditions alive in the face of the rapidly-encroaching modernization of the American Southwest.</p>';
break;
case 'url:https://standardebooks.org/ebooks/graham-greene/the-man-within':
$description = '<p><i>The Man Within</i> is acclaimed novelist Graham Greenes first novel. Set against the backdrop of the English countryside,, the novel explores themes of guilt, redemption, the nature of courage and cowardice, and the complex relationship between ones inner beliefs and outward actions.</p>';
$order = 7;
break;
case 'url:https://standardebooks.org/ebooks/calvin-coolidge/the-autobiography-of-calvin-coolidge':
$description = '<p>Calvin Coolidge was the 30th president of the United States, entering the office as vice president when president Warren G. Harding suddenly passed, and winning a reelection term. Even though he was hugely popular, he declined running for a second full term, opting to retire instead. In this autobiography—which is as brief as “Silent Cals” legend suggests it might be—we follow the former president from his idyllic boyhood in Vermont, to a career in the law, to the governership of Massachussetts, to the presidency and beyond.</p>';
$order = 8;
$description = '<p>Calvin Coolidge was the 30th president of the United States, entering the office as vice president when president Warren G. Harding suddenly passed, and winning a reelection term. Even though he was hugely popular, he declined running for a second full term, opting to retire instead. In this autobiography—which is as brief as “Silent Cals” legend suggests it might be—we follow the former president from his idyllic boyhood in Vermont, to a career in the law, to the governorship of Massachusetts, to the presidency and beyond.</p>';
break;
case 'url:https://standardebooks.org/ebooks/lloyd-c-douglas/magnificent-obsession':
$description = '<p>Robert Merrick, a young man from a wealthy family, accidentally causes the death of an esteemed neurosurgeon. Wracked by guilt, Robert decides to devote his own life to improving the life of others.</p><p><i>Magnificent Obsession</i> was a hugely popular work in its time, inspiring a blockbuster 1935 film of the same name.</p>';
$order = 9;
break;
case 'url:https://standardebooks.org/ebooks/josephine-tey/the-man-in-the-queue':
$description = '<p>Standing in line in a long queue for a show at a theater, a young man is stabbed in the back. <a href="/collections/inspector-grant">Inspector Alan Grant</a> of the Metropolitan Police is soon on the case, though he finds it deeply puzzling—not least because the identity of the victim is itself a mystery.</p><p><i>The Man in the Queue</i> was the first in a series of hugely successful detective novels by Josephine Tey.</p>';
$order = 10;
break;
case 'url:https://standardebooks.org/ebooks/john-buchan/the-courts-of-the-morning':
$description = '<p><i>The Courts of the Morning</i> opens with <a href="/collections/richard-hannay">Major-General Richard Hannay</a> being approached by American diplomats regarding the disappearance of a wealthy industrialist. He in turn seeks the help of his friend Sandy Arbuthnot—but Arbuthnot himself quickly goes missing. We soon head to the South American country of Olifa, where a powerful head of a mining company is gradually enslaving the populace. It seems that only guerrilla warfare will save the country from rule under a ruthless tyrant.</p>';
$order = 11;
break;
case 'url:https://standardebooks.org/ebooks/j-b-priestley/the-good-companions':
$description = '<p>The recipient of the <a href="/collections/james-tait-black-memorial-fiction-prize-winners">James Tait Black Memorial Fiction Prize</a>, <i>The Good Companions</i> was a blockbuster novel that made J. B. Priestleys reputation. In it we follow three protagonists from different walks of life who, looking for a change of pace, strike out from home. They eventually cross paths with each other and with a group of “concert players,” a type of traveling vaudeville troupe common in the day. They decide to join forces and form the “Good Companions,” a musical act that takes them on a series of cozy adventures.</p>';
$order = 12;
break;
case 'url:https://standardebooks.org/ebooks/dashiell-hammett/the-dain-curse':
$description = '<p>In <i>The Dain Curse</i>, the second <a href="/collections/continental-op">Continental Op</a> novel, the legendary but nameless hard-drinking and quick-shooting detective is sent to investigate the theft of diamonds from a San Franciso family. The fast-paced noir thriller quickly veers from car chases to cultists to the supernatural, but the unflappable Continental Op is relentless in his pursuit of truth.</p>';
$order = 13;
$description = '<p>In <i>The Dain Curse</i>, the second <a href="/collections/continental-op">Continental Op</a> novel, the legendary but nameless hard-drinking and quick-shooting detective is sent to investigate the theft of diamonds from a San Francisco family. The fast-paced noir thriller quickly veers from car chases to cultists to the supernatural, but the unflappable Continental Op is relentless in his pursuit of truth.</p>';
break;
case 'url:https://standardebooks.org/ebooks/c-s-forester/brown-on-resolution':
$description = '<p>While on operations in the Pacific during the first World War, the sailor Albert Browns ship is sunk—but he survives, and is taken on board the German cruiser that sank them. It too has suffered damage, and heads to some nearby islands for repairs. In this unlikely and hostile setting, Brown, alone, pits himself against the German ship and its crew, seeking to delay its progress while British naval reinforcements rush to his rescue.</p><p>Foresters careful historical research adds an unimpeachable air of versimilitude to the novel, and indeed, the plot is loosely based on real events.</p>';
$order = 14;
$description = '<p>While on operations in the Pacific during the first World War, the sailor Albert Browns ship is sunk—but he survives, and is taken on board the German cruiser that sank them. It too has suffered damage, and heads to some nearby islands for repairs. In this unlikely and hostile setting, Brown, alone, pits himself against the German ship and its crew, seeking to delay its progress while British naval reinforcements rush to his rescue.</p><p>Foresters careful historical research adds an unimpeachable air of verisimilitude to the novel, and indeed, the plot is loosely based on real events.</p>';
break;
case 'url:https://standardebooks.org/ebooks/arthur-conan-doyle/the-maracot-deep':
$description = '<p>While investigating the deepest part of the Atlantic Ocean, a team led by Dr. Maracot is cut off from their ship and hurled to the bottom of the ocean. There, they find themselves in the remnants of the ancient civilization of Atlantis.</p><p>Though Doyle is most famous for his <a href="/collections/sherlock-holmes">Sherlock Holmes</a> stories, in which a brilliant logician uses reason and deduction to solve crime, in later years he became deeply spiritual. This novel, written just a year before his death, combines his interest in science and reason with his new spiritual outlook.</p>';
$order = 15;
break;
}
$ebooksWithDescriptions[$order] = ['ebook' => $ebook, 'description' => $description];
$ebooksWithDescriptions[array_search($ebook->Identifier, $identifiers)] = ['ebook' => $ebook, 'description' => $description];
}
ksort($ebooksWithDescriptions);
?><?= Template::Header(['title' => 'Public Domain Day 2025 - Blog', 'highlight' => '', 'description' => 'Read about the new ebooks Standard Ebooks is releasing for Public Domain Day 2025!', 'css' => ['/css/public-domain-day.css']]) ?>
?><?= Template::Header(['title' => 'Happy Public Domain Day 2025! - Blog', 'highlight' => '', 'description' => 'Read about the new ebooks Standard Ebooks is releasing for Public Domain Day 2025!', 'css' => ['/css/public-domain-day.css']]) ?>
<main>
<section class="narrow">
<h1>Public Domain Day 2025</h1>
<h1>Happy Public Domain Day 2025!</h1>
<?= Template::DonationCounter() ?>
<?= Template::DonationProgress() ?>
<p>Happy Public Domain Day!</p>
<p>Around the world, people celebrate Public Domain Day on January 1, the day in which copyright expires on some older works and they enter the public domain in many different countries.</p>
<p>Around the world, people celebrate Public Domain Day on January 1, the day in which copyright expires on some older works, putting them in the public domain in many different countries.</p>
<p>Some countries have a copyright term of the life of the author plus seventy years. These countries have been celebrating public domain day for some time now.</p>
<p>But in the U.S., powerful corporations have continuously extended the length of copyright for decades so they could continue to profit. In the process, they locked <em>all</em> of our cultural output away from us for nearly a century.</p>
<p>2019 was the year in which this extremely long, corporate-dictated copyright term was finally going to end. As that year approached, we had every reason to assume that these powerful corporations would once again lobby to extend copyright even further.</p>
<p>But as 2019 grew closer, it became clear that these corporations <em>wouldnt</em> work to extend copyright yet again—making 2019 the first year in almost a century in which a signifcant amount of literature once again entered the public domain in the U.S., free for anyone in the U.S. to read, use, and share.</p>
<p>In the U.S. Constitution, copyright terms were limited to up to twenty-eight years, in order to “promote the Progress of Science and useful Arts. But since the Constitution was drafted, powerful corporations have continuously extended the length of copyright so they could continue to profit. The result is that today in the U.S., work only enters the public domain ninety-five years after publication. This has locked <em>all</em> of our cultural output away from us for <em>nearly a century</em>.</p>
<p>2019 was the year in which the first works from almost a hundred years ago were finally scheduled to enter the public domain, ending this long, corporate-dictated cultural winter. As that year approached, we had every reason to assume that these powerful corporations would once again lobby to extend copyright even further.</p>
<p>But as 2019 grew closer, it became clear that these corporations <em>wouldnt</em> work to extend copyright yet again—making 2019 the first year in almost a century in which a significant amount of art and literature once again entered the public domain in the U.S., free for anyone in the U.S. to read, use, and share.</p>
<p>Ever since then, each year weve been joining our international friends in celebrating Public Domain Day by preparing some of the biggest literary hits of the year for you to read on January 1.</p>
<hr/>
<p><strong>In 2025, books published in 1929 enter the U.S. public domain.</strong> And 1929 was a literary doozy!</p>
<p>Books by William Faulkner, Ernest Hemingway, Mahatma Gandhi, and John Steinbeck entered the U.S. public domain. We also have the first novel featuring the Continental Op, the archetype for every hard-boiled noir detective to grace page and screen since; the English translation of <i>All Quiet on the Western Front</i>; and much more.</p>
<p>Our friends at Public Domain Review have written about some <a href="">other things besides literature that entered the public domain this year</a>.</p>
<p>At Standard Ebooks, our volunteers have been working hard for the past few months to prepare the following ebooks to be ready for Public Domain Day. Read more about them and download them for free:</p>
<p><strong>In 2025, books published in 1929 enter the U.S. public domain.</strong></p>
<p>And 1929 was a literary doozy!</p>
<p>Books by William Faulkner, Ernest Hemingway, Mahatma Gandhi, and John Steinbeck enter the U.S. public domain. Joining these esteemed names is the first novel featuring the Continental Op, the nameless hard-boiled noir detective who created the archetype for every hard-drinking, fedora-wearing private eye to grace page and screen since; the English translation of <i>All Quiet on the Western Front</i>; and much more.</p>
<p>Our friends at Public Domain Review have written about some <a href="">other things that enter the public domain this year, too</a>.</p>
<p>At Standard Ebooks, our volunteers have been working hard for the past few months to prepare the following ebooks to be ready for Public Domain Day. Join us in celebrating the freeing of our cultural heritage by downloading these ebooks and reading them for free:</p>
<? if(sizeof($ebooksWithDescriptions) == 0){ ?>
<p class="empty">Were still preparing these free ebooks for Public Domain Day. Check back after January 1!</p>
<? }else{ ?>
<ul class="public-domain-day">
<? foreach($ebooksWithDescriptions as $ebookGroup){ ?>
<li>
@ -128,13 +115,21 @@ ksort($ebooksWithDescriptions);
</a>
</div>
<div>
<h2><a href="<?= $ebookGroup['ebook']->Url ?>"><?= Formatter::EscapeHtml($ebookGroup['ebook']->Title) ?></a></h2>
<h2>
<a href="<?= $ebookGroup['ebook']->Url ?>"><?= Formatter::EscapeHtml($ebookGroup['ebook']->Title) ?></a>
</h2>
<p class="byline">by <a href="<?= $ebookGroup['ebook']->AuthorsUrl ?>"><?= Formatter::EscapeHtml($ebookGroup['ebook']->Authors[0]->Name) ?></a></p>
<p><?= $ebookGroup['description'] ?></p>
<p>
<?= $ebookGroup['description'] ?>
</p>
<p>
<a href="<?= $ebookGroup['ebook']->Url ?>">Download and read for free </a>
</p>
</div>
</li>
<? } ?>
</ul>
<? } ?>
</section>
</main>
<?= Template::Footer() ?>

View file

@ -2799,12 +2799,11 @@ progress.stretch::-moz-progress-bar{
border-left: 5px solid #E9C91F;
}
aside button.close{
button.close{
display: block;
font-size: 0;
border: none;
box-shadow: none;
padding: 0;
position: absolute;
top: 0;
right: 0;
@ -2819,11 +2818,12 @@ aside button.close{
margin: 0;
}
aside button.close:hover{
button.close:hover{
opacity: 1;
background-color: transparent;
}
aside button.close:active{
button.close:active{
left: auto;
top: 1px;
right: -1px;
@ -3071,6 +3071,35 @@ hr + p{
margin-top: 0;
}
.public-domain-day-banner{
width: 100%;
text-align: center;
padding: 1rem;
background: #d37a71;
margin-bottom: 1.5rem;
color: #fff;
text-shadow: 1px 1px 0px rgba(0, 0, 0, .75);
position: relative;
overflow: hidden;
box-sizing: border-box;
box-shadow: 0 0 1rem rgba(0, 0, 0, .75);
}
.public-domain-day-banner button.close{
background-image: url('/images/close-dark.svg');
top: 2px;
right: 2px;
}
body > header:has(.public-domain-day-banner){
flex-wrap: wrap;
padding-top: 0;
}
form[action="/settings"] label{
display: inline-block;
}
@media (hover: none) and (pointer: coarse){ /* target ipads and smartphones without a mouse */
/* For iPad, unset the height so it matches the other elements */
select[multiple]{
@ -3267,6 +3296,10 @@ hr + p{
.downloads-container figure + div{
padding-left: 0;
}
.public-domain-day-banner strong{
display: block;
}
}
@media(max-width: 900px){
@ -3845,24 +3878,116 @@ hr + p{
box-shadow: none;
}
figure.realistic-ebook:hover img,
.ebook .downloads-container:hover figure img{ /* cover */
filter: brightness(1.15);
transform: translateY(-.5rem);
}
figure.realistic-ebook:hover picture::after,
.ebook .downloads-container:hover figure picture::after{ /* back board */
transform: translateY(calc(-1 * (var(--size) + .5rem)));
}
.realistic-ebook:hover picture::before,
.ebook .downloads-container:hover picture::before{ /* pages */
transform: rotate(90deg) skewy(-45deg) translateX(-.5rem) translateY(-.5rem);
}
figure.realistic-ebook:hover::after,
.ebook .downloads-container:hover figure::after{ /* spine */
transform: skewY(45deg) translateY(-.5rem);
}
figure.realistic-ebook:hover::before
.ebook .downloads-container:hover figure::before{ /* shadow */
transform: skewX(45deg) scale(1.05);
}
#confettis{
top: 0;
overflow: hidden;
position: absolute;
height: 100%;
width: 100%;
pointer-events: none;
}
.confetti{
left: 50%;
width: 16px;
height: 16px;
position: absolute;
transform-origin: left top;
animation: confetti 5s ease-in-out -2s infinite;
}
@keyframes confetti{
0%{
transform: rotateZ(15deg) rotateY(0deg) translate(0, 0);
}
25%{
transform: rotateZ(5deg) rotateY(360deg) translate(-5vw, 20vh);
}
50%{
transform: rotateZ(15deg) rotateY(720deg) translate(5vw, 60vh);
}
75%{
transform: rotateZ(5deg) rotateY(1080deg) translate(-10vw, 80vh);
}
100%{
transform: rotateZ(15deg) rotateY(1440deg) translate(10vw, 110vh);
}
}
.confetti:nth-child(1){
left: 10%;
animation-delay: 0;
background-color: #fc0120;
}
.confetti:nth-child(2){
left: 20%;
animation-delay: -5s;
background-color: #8257e6;
}
.confetti:nth-child(3){
left: 30%;
animation-delay: -3s;
background-color: #ffbf4d;
}
.confetti:nth-child(4){
left: 40%;
animation-delay: -2.5s;
background-color: #fe5d7a;
}
.confetti:nth-child(5){
left: 50%;
animation-delay: -4s;
background-color: #45ec9c;
}
.confetti:nth-child(6){
left: 60%;
animation-delay: -6s;
background-color: #f6e327;
}
.confetti:nth-child(7){
left: 70%;
animation-delay: -1.5s;
background-color: #f769ce;
}
.confetti:nth-child(8){
left: 80%;
animation-delay: -2s;
background-color: #007de7;
}
.confetti:nth-child(9){
left: 90%;
animation-delay: -3.5s;
background-color: #63b4fc;
}
.confetti:nth-child(10){
left: 100%;
animation-delay: -2.5s;
background-color: #f9c4ea;
}
}

View file

@ -90,8 +90,9 @@ h1,h2,h3,h4,h5,h6{
text-shadow: 2px 2px 0 rgba(0, 0, 0, .75);
}
aside button.close{
filter: invert(1);
button.close{
/* Can't use `filter: invert()` because that also inverts the focus outline. */
background-image: url("/images/close-dark.svg");
}
.donation .flipboard span{

View file

@ -11,6 +11,11 @@
margin-left: 1rem;
}
.public-domain-day div + div > p:last-child{
text-align: right;
font-style: italic;
}
.public-domain-day h2{
margin-top: 0;
}
@ -31,6 +36,12 @@
margin-top: -1rem;
}
.empty{
text-align: center;
font-style: italic;
margin-top: 2rem;
}
@media(max-width: 1000px){
.public-domain-day div + div{
margin-left: 0;

View file

@ -4,7 +4,7 @@ use Safe\DateTimeImmutable;
// If the user is not logged in, or has less than some amount of downloads, show a thank-you page.
$ebook = null;
$downloadCount = $_COOKIE['download-count'] ?? 0;
$downloadCount = HttpInput::Int(COOKIE, 'download-count') ?? 0;
$showThankYouPage = Session::$User === null && $downloadCount < 5;
$downloadUrl = null;

16
www/images/close-dark.svg Normal file
View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="10"
height="10"
viewBox="0 0 10 10"
version="1.1"
xmlns="http://www.w3.org/2000/svg">
<path
style="fill:none;stroke:#ffffff;stroke-width:1.7522;"
d="M 0.61949671,0.61949 9.3805033,9.3805"
/>
<path
style="fill:none;stroke:#ffffff;stroke-width:1.7522;"
d="M 9.3805033,0.61949 0.61949672,9.3805"
/>
</svg>

After

Width:  |  Height:  |  Size: 418 B

View file

@ -31,7 +31,7 @@ if($exception){
<form action="/newsletter/subscriptions" method="<?= Enums\HttpMethod::Post->value ?>">
<label class="automation-test"><? /* Test for spam bots filling out all fields */ ?>
<input type="text" name="automationtest" value="" maxlength="80" />
<input type="text" name="automation-test" value="" maxlength="80" />
</label>
<label>Your email address
<input type="email" name="email" value="<?= Formatter::EscapeHtml($subscription->User->Email ?? '') ?>" maxlength="80" required="required" />

View file

@ -11,7 +11,7 @@ try{
$subscription = new NewsletterSubscription();
if(HttpInput::Str(POST, 'automationtest')){
if(HttpInput::Str(POST, 'automation-test')){
// A bot filled out this form field, which should always be empty. Pretend like we succeeded.
if($requestType == Enums\HttpRequestType::Web){
http_response_code(Enums\HttpCode::SeeOther->value);

View file

@ -1,17 +1,18 @@
<?
$colorScheme = $_COOKIE['color-scheme'] ?? 'auto';
$colorScheme = Enums\ColorSchemeType::tryFrom(HttpInput::Str(COOKIE, 'color-scheme') ?? Enums\ColorSchemeType::Auto->value);
?><?= Template::Header(['title' => 'Website Settings', 'description' => 'Adjust your settings for viewing the Standard Ebooks website.']) ?>
<main>
<h1>Website Settings</h1>
<form action="/settings" method="<?= Enums\HttpMethod::Post->value ?>">
<input type="hidden" name="_method" value="<?= Enums\HttpMethod::Patch->value ?>" />
<label>
<span>Color scheme</span>
<span>
<select name="color-scheme">
<option value="auto"<? if($colorScheme == 'auto'){ ?> selected="selected"<? } ?>>Automatic</option>
<option value="light"<? if($colorScheme == 'light'){ ?> selected="selected"<? } ?>>Light</option>
<option value="dark"<? if($colorScheme == 'dark'){ ?> selected="selected"<? } ?>>Dark</option>
<option value="<?= Enums\ColorSchemeType::Auto->value ?>"<? if($colorScheme == Enums\ColorSchemeType::Auto){ ?> selected="selected"<? } ?>>Automatic</option>
<option value="<?= Enums\ColorSchemeType::Light->value ?>"<? if($colorScheme == Enums\ColorSchemeType::Light){ ?> selected="selected"<? } ?>>Light</option>
<option value="<?= Enums\ColorSchemeType::Dark->value ?>"<? if($colorScheme == Enums\ColorSchemeType::Dark){ ?> selected="selected"<? } ?>>Dark</option>
</select>
</span>
</label>

View file

@ -1,24 +1,29 @@
<?
use Safe\DateTimeImmutable;
$httpMethod = HttpInput::ValidateRequestMethod([Enums\HttpMethod::Patch]);
// PATCHing settings.
if($httpMethod == Enums\HttpMethod::Patch){
$hideDonationAlert = HttpInput::Bool(POST, 'hide-donation-alert');
$colorScheme = HttpInput::Str(POST, 'color-scheme');
$hidePublicDomainDayBanner = HttpInput::Bool(POST, 'hide-public-domain-day-banner');
$colorScheme = Enums\ColorSchemeType::tryFrom(HttpInput::Str(POST, 'color-scheme') ?? '');
if($hideDonationAlert !== null){
setcookie('hide-donation-alert', $hideDonationAlert ? 'true' : 'false', ['expires' => intval((new DateTimeImmutable('+1 month'))->format(Enums\DateTimeFormat::UnixTimestamp->value)), 'path' => '/', 'domain' => SITE_DOMAIN, 'secure' => true, 'httponly' => true, 'samesite' => 'Lax']);
}
if($colorScheme !== null){
if($colorScheme !== 'dark' && $colorScheme !== 'light' && $colorScheme !== 'auto'){
$colorScheme = 'auto';
if($hidePublicDomainDayBanner !== null){
setcookie('hide-public-domain-day-banner', $hidePublicDomainDayBanner ? 'true' : 'false', ['expires' => intval((new DateTimeImmutable('+1 month'))->format(Enums\DateTimeFormat::UnixTimestamp->value)), 'path' => '/', 'domain' => SITE_DOMAIN, 'secure' => true, 'httponly' => true, 'samesite' => 'Lax']);
}
if($colorScheme == 'auto'){
if($colorScheme !== null){
if($colorScheme == Enums\ColorSchemeType::Auto){
// Delete the cookie; auto is the default
setcookie('color-scheme', '', ['expires' => 0, 'path' => '/', 'domain' => SITE_DOMAIN, 'secure' => true, 'httponly' => true, 'samesite' => 'Lax']);
}
else{
setcookie('color-scheme', $colorScheme, ['expires' => intval((new DateTimeImmutable('+1 year'))->format(Enums\DateTimeFormat::UnixTimestamp->value)), 'path' => '/', 'domain' => SITE_DOMAIN, 'secure' => true, 'httponly' => true, 'samesite' => 'Lax']);
setcookie('color-scheme', $colorScheme->value, ['expires' => intval((new DateTimeImmutable('+1 year'))->format(Enums\DateTimeFormat::UnixTimestamp->value)), 'path' => '/', 'domain' => SITE_DOMAIN, 'secure' => true, 'httponly' => true, 'samesite' => 'Lax']);
}
}
@ -27,4 +32,4 @@ http_response_code(Enums\HttpCode::SeeOther->value);
$redirect = $_SERVER['HTTP_REFERER'] ?? '/';
header('Location: ' . $redirect);
?>
}

View file

@ -20,12 +20,12 @@ print("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
<url>
<loc><?= SITE_URL ?>/authors</loc>
</url>
<!-- <url>
<url>
<loc><?= SITE_URL ?>/blog</loc>
</url> -->
<!-- <url>
</url>
<url>
<loc><?= SITE_URL ?>/blog/public-domain-day-2025</loc>
</url> -->
</url>
<? foreach($authors as $author){ ?>
<url>
<loc><?= SITE_URL ?><?= $author->Url ?></loc>