HN Skins

Read HN in style!

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name        HN Skins
// @version     0.3.0
// @description Read HN in style!
// @author      Susam Pal
// @match       https://news.ycombinator.com/*
// @namespace   susam
// @license     MIT
// ==/UserScript==
(function () {
  const skins = {
    Cafe: skCafe,
    Courier: skCourier,
    London: skLondon,
    Midnight: skMidnight,
    Terminal: skTerminal,
    None: null
  }

  function skCafe () {
    const style = document.createElement('style')
    style.className = 'skStyle skCafe'
    style.textContent = `
      body {
        background: #ffe;
      }
      * a:visited {
        color: #754 !important;
      }
      td[bgcolor] *, * a:link, td, span, div, font, input, textarea {
        color: #400 !important;
      }
      .c5A, .c73 {
        color: #633 !important;
      }
      .c88, .c9C {
        color: #966 !important;
      }
      .cBE, .cDD {
        color: #c99 !important;
      }
      td[bgcolor] {
        background: #fa3;
      }
      table#hnmain {
        background: #fec;
      }
      td[bgcolor] * {
        color: #000 !important;
      }
      td, span, div, font, input, textarea {
        font-family: monospace, monospace !important;
      }
      input, textarea {
        background: #fc9;
      }
      .hnuser font { /* green user */
        color: #060 !important;
      }
      .topsel {
        font-weight: bold;
      }
    `
    spacingStyle()
    window.document.head.append(style)
  }

  function skCourier () {
    const style = document.createElement('style')
    style.className = 'skStyle skCourier'
    style.textContent = `
      * a:visited {
        color: #666 !important;
      }
      td[bgcolor] *, * a:link, td, span, div, font, input, textarea {
        color: #000 !important;
      }
      .c5A, .c73 {
        color: #666 !important;
      }
      .c88, .c9C {
        color: #999 !important;
      }
      .cBE, .cDD {
        color: #ccc !important;
      }
      td[bgcolor] {
        background: #eee;
        border: thin solid #666;
      }
      table#hnmain {
        background: #fff;
      }
      td[bgcolor] * {
        color: #000 !important;
      }
      td, span, div, font, input, textarea {
        font-family: courier, monospace !important;
      }
      img {
        filter: saturate(0);
      }
      .hnuser font { /* green user */
        color: #060;
      }
      .topsel {
        font-weight: bold;
      }
    `
    spacingStyle()
    window.document.head.append(style)
  }

  function skLondon () {
    const style = document.createElement('style')
    style.className = 'skStyle skLondon'
    style.textContent = `
      body {
        background: #eee;
      }
      * a:visited {
        color: #666 !important;
      }
      td[bgcolor] *, * a:link, td, span, div, font, input, textarea {
        color: #000 !important;
      }
      .c5A, .c73 {
        color: #666 !important;
      }
      .c88, .c9C {
        color: #999 !important;
      }
      .cBE, .cDD {
        color: #ccc !important;
      }
      td[bgcolor] {
        background: #333;
      }
      td[bgcolor] * {
        color: #fff !important;
      }
      table#hnmain {
        background: #fff;
      }
      td, span, div, font, input, textarea {
        font-family: georgia, times, serif !important;
        font-size: large !important;
        color: #000;
      }
      img {
        filter: saturate(0);
        margin-right: 0.5em;
      }
      .topsel {
        font-weight: bold;
      }
      .hnuser font { /* green user */
        color: #060;
      }
    `
    spacingStyle()
    window.document.head.append(style)
  }

  function skMidnight () {
    const style = document.createElement('style')
    style.className = 'skStyle skMidnight'
    style.textContent = `
      body {
        background: #111;
      }
      * a:visited {
        color: #888 !important;
      }
      td[bgcolor] *, * a:link, td, span, div, font, input, textarea {
        color: #ccc !important;
      }
      .c5A, .c73 {
        color: #999 !important;
      }
      .c88, .c9C {
        color: #666 !important;
      }
      .cBE, .cDD {
        color: #333 !important;
      }
      td[bgcolor] {
        background: #333;
      }
      table#hnmain {
        background: #000;
      }
      img {
        filter: saturate(0);
      }
      input, textarea {
        background: #000;
      }
      .hnuser font { /* green user */
        color: #3c3 !important;
      }
      .topsel {
        font-weight: bold;
      }
    `
    spacingStyle()
    window.document.head.append(style)
  }

  function skTerminal () {
    const style = document.createElement('style')
    style.className = 'skStyle skTerminal'
    style.textContent = `
      body {
        background: #020;
      }
      * a:visited {
        color: #0b0 !important;
      }
      td[bgcolor] *, * a:link, td, span, div, font, input, textarea {
        color: #3f3 !important;
      }
      .c5A, .c73 {
        color: #3c3 !important;
      }
      .c88, .c9C {
        color: #393 !important;
      }
      .cBE, .cDD {
        color: #363 !important;
      }
      td[bgcolor] {
        background: #030;
      }
      table#hnmain {
        background: #010;
      }
      td, span, div, font, input, textarea {
        font-family: monospace, monospace !important;
      }
      img {
        filter: hue-rotate(75deg);;
      }
      input, textarea {
        background: #000;
      }
      .hnuser font { /* green user */
        color: #f9f !important;
      }
      .topsel {
        font-weight: bold;
      }
      .votearrow {
        filter: sepia(1) hue-rotate(90deg) saturate(3);
      }
    `
    spacingStyle()
    window.document.head.append(style)
  }

  function spacingStyle () {
    const style = document.createElement('style')
    style.className = 'skStyle spacing'
    style.textContent = `
      .spacer {
        height: 1.25em !important;
      }
      td, span, div, font, input, textarea {
        font-size: medium !important;
      }
      p {
        margin: 1em 0 !important;
      }
      .pagetop {
        line-height: 1.5em;
      }
      .pagetop b {
        margin-right: 1em;
      }
      .subtext {
        padding-top: 0.5em;
      }
      .toptext {
        padding-top: 0.5em;
      }
      .toptext, .comment {
        line-height: 1.5em;
      }
      .reply p {
        margin-bottom: 0.5em !important;
      }
      #bigbox > td {
        padding-top: 0.5em;
      }
    `
    window.document.head.append(style)
  }

  function dialogStyle () {
    const style = document.createElement('style')
    style.className = 'skStyle dialog'
    style.textContent = `
      dialog {
        background: #fec;
        border: thick double #653;
        padding: 1.5em;
      }
      dialog * {
        color: #431;
        font-family: courier, monospace;
        font-size: medium;
      }
      dialog h1 {
        font-size: 1.5em;
        margin: 0;
        text-align: center;
      }
      dialog button {
        font-family: courier, monospace;
        font-size: medium;
        display: block;
        margin: 0 auto;
        min-width: 10em;
        background: #edb;
        padding: 0.25em;
      }
      dialog button:active {
        background: #cb9;
      }
      #skinOptions {
        display: grid;
        grid-template-columns: 1fr 1fr;
        grid-template-rows: repeat(3, auto);
        grid-auto-flow: column;
        gap: 1em 2em;
        margin: 1.5em 0;
      }
    `
    window.document.head.append(style)
  }

  function loadSkin () {
    for (const style of document.querySelectorAll('.skStyle')) {
      style.remove()
    }
    const name = window.localStorage.getItem('hnskin')
    console.log('Last saved skin:', name)
    if (name === null || skins[name] === null || skins[name] === undefined) {
      console.log('Forgetting invalid skin:', name)
      window.localStorage.removeItem('hnskin')
    } else {
      console.log('Loading skin:', name)
      skins[name]()
    }
    dialogStyle()
  }

  function handleRadio (e) {
    const name = e.target.value
    if (name === 'None') {
      window.localStorage.removeItem('hnskin')
    } else {
      window.localStorage.setItem('hnskin', name)
    }
    loadSkin()
  }

  function effectiveSkinName () {
    return window.localStorage.getItem('hnskin') || 'None'
  }

  function initDialog () {
    const dialog = document.createElement('dialog')
    dialog.id = 'dialog'
    document.body.append(dialog)

    const h1 = document.createElement('h1')
    h1.textContent = 'Select Skin'
    dialog.append(h1)

    const form = document.createElement('form')
    form.method = 'dialog'
    dialog.append(form)

    const divOptions = document.createElement('div')
    divOptions.id = 'skinOptions'
    form.append(divOptions)

    for (const key of Object.keys(skins)) {
      const div = document.createElement('div')
      divOptions.append(div)

      const input = document.createElement('input')
      input.id = 'sk' + key
      input.type = 'radio'
      input.name = 'skin'
      input.value = key
      input.checked = effectiveSkinName() === key
      input.addEventListener('change', handleRadio)
      div.append(input)

      const label = document.createElement('label')
      label.htmlFor = 'sk' + key
      label.textContent = key
      div.append(label)
    }

    const button = document.createElement('button')
    button.textContent = 'Close'
    form.append(button)
  }

  function showDialog (e) {
    e.preventDefault()
    const dialog = document.getElementById('dialog')
    dialog.showModal()
  }

  function init () {
    if (document.getElementById('skinOptions')) {
      return
    }

    initDialog()

    const top = document.querySelector('.pagetop')
    if (top !== null) {
      const anchor = document.createElement('a')
      anchor.href = '#'
      anchor.textContent = 'skins'
      anchor.addEventListener('click', showDialog)
      top.append(' | ', anchor)
    }
    loadSkin()
  }

  init()
})()