Google adds styles to the maps container that override my styles.
I know how to fix this. But the API (v3.8/9/exp) also loads the webfont "Roboto" which I don't really need/want.
Is there any setting/option/way around this?
Can I prevent the API from adding the extra CSS?
This is the code the google-maps-API adds to the <head> of my page:
<style type="text/css">
.gm-style .gm-style-cc span,
.gm-style .gm-style-cc a,
.gm-style .gm-style-mtc div {
font-size:10px
}
</style>
<link type="text/css"
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700">
<style type="text/css">
#media print {
.gm-style .gmnoprint,
.gmnoprint {
display:none
}
}
#media screen {
.gm-style .gmnoscreen,
.gmnoscreen {
display:none
}
}
</style>
<style type="text/css">
.gm-style {
font-family: Roboto,Arial,sans-serif;
font-size: 11px;
font-weight: 400;
text-decoration: none
}
</style>
You can replace the insertBefore method before the Google script invokes it:
http://jsfiddle.net/coma/7st6d9p2/
var head = document.getElementsByTagName('head')[0];
// Save the original method
var insertBefore = head.insertBefore;
// Replace it!
head.insertBefore = function (newElement, referenceElement) {
if (newElement.href && newElement.href.indexOf('//fonts.googleapis.com/css?family=Roboto') > -1) {
console.info('Prevented Roboto from loading!');
return;
}
insertBefore.call(head, newElement, referenceElement);
};
// Check it!
new google.maps.Map(document.getElementById('map'), {
center : new google.maps.LatLng(51.508742,-0.120850),
zoom : 16,
mapTypeId : google.maps.MapTypeId.ROADMAP,
streetViewControl: false,
zoomControl : false,
panControl : false,
mapTypeControl : false
});
UPDATE 10/2017
Google changed the approach of how they inject the styles on the page. Currently they inserting an empty style element and then changing the contents of this style element with Robot font. Here is a new solution:
// Preventing the Google Maps libary from downloading an extra font
(function() {
var isRobotoStyle = function (element) {
// roboto font download
if (element.href
&& element.href.indexOf('https://fonts.googleapis.com/css?family=Roboto') === 0) {
return true;
}
// roboto style elements
if (element.tagName.toLowerCase() === 'style'
&& element.styleSheet
&& element.styleSheet.cssText
&& element.styleSheet.cssText.replace('\r\n', '').indexOf('.gm-style') === 0) {
element.styleSheet.cssText = '';
return true;
}
// roboto style elements for other browsers
if (element.tagName.toLowerCase() === 'style'
&& element.innerHTML
&& element.innerHTML.replace('\r\n', '').indexOf('.gm-style') === 0) {
element.innerHTML = '';
return true;
}
// when google tries to add empty style
if (element.tagName.toLowerCase() === 'style'
&& !element.styleSheet && !element.innerHTML) {
return true;
}
return false;
}
// we override these methods only for one particular head element
// default methods for other elements are not affected
var head = $('head')[0];
var insertBefore = head.insertBefore;
head.insertBefore = function (newElement, referenceElement) {
if (!isRobotoStyle(newElement)) {
insertBefore.call(head, newElement, referenceElement);
}
};
var appendChild = head.appendChild;
head.appendChild = function (textNode) {
if (!isRobotoStyle($(textNode)[0])) {
appendChild.call(head, textNode);
}
};
})();
ORIGINAL ANSWER
Thanks to coma for the solution! I also decided to intercept styles which override the font-family, font-size and font-weight. The complete solution for modern browsers and IE8+:
// Preventing the Google Maps libary from downloading an extra font
var head = $('head')[0];
var insertBefore = head.insertBefore;
head.insertBefore = function (newElement, referenceElement) {
// intercept font download
if (newElement.href
&& newElement.href.indexOf('https://fonts.googleapis.com/css?family=Roboto') === 0) {
return;
}
// intercept style elements for IEs
if (newElement.tagName.toLowerCase() === 'style'
&& newElement.styleSheet
&& newElement.styleSheet.cssText
&& newElement.styleSheet.cssText.replace('\r\n', '').indexOf('.gm-style') === 0) {
return;
}
// intercept style elements for other browsers
if (newElement.tagName.toLowerCase() === 'style'
&& newElement.innerHTML
&& newElement.innerHTML.replace('\r\n', '').indexOf('.gm-style') === 0) {
return;
}
insertBefore.call(head, newElement, referenceElement);
};
I found above solution to prevent websites with Google Maps from loading Roboto.
If you - like I do - use Wordpress, there might be other plugins referring to Google Fonts.
However, I struggled on some of my websites with the above code, since parts of it (1) affected also other styles to load, (2) "killed" styles, which intentionally not only contained gm-style, but other styles as well and (3) not affected other Google Fonts to load, where one or another plugin added links to fonts.googleapis.com by DOM-manipulation as well.
The below worked for me. It simply prevents other scripts from adding any tag having https://fonts.googleapis.com in it's href-attribute.
(function($) {
var isGoogleFont = function (element) {
// google font download
if (element.href
&& element.href.indexOf('https://fonts.googleapis.com') === 0) {
return true;
}
return false;
}
// we override these methods only for one particular head element
// default methods for other elements are not affected
var head = $('head')[0];
var insertBefore = head.insertBefore;
head.insertBefore = function (newElement, referenceElement) {
if (!isGoogleFont(newElement)) {
insertBefore.call(head, newElement, referenceElement);
}
};
var appendChild = head.appendChild;
head.appendChild = function (textNode) {
if (!isGoogleFont($(textNode)[0])) {
appendChild.call(head, textNode);
}
};
})(jQuery);
Unfortunately, I'm a newb and I couldn't get the other suggestions to work. So I removed all the Google fonts from the DOM. I hope it helps.
const googleFont = document.querySelector('link[rel="stylesheet"][href*="fonts.googleapis.com"]');
if (googleFont) {
googleFont.remove();
}
For TypeScript a solution would be:
const head = document.head;
const insertBefore = head.insertBefore;
head.insertBefore = <T extends Node>(
newElement: T,
referenceElement: Node
): T => {
if (
newElement instanceof Element &&
newElement?.hasAttribute('href') &&
newElement?.getAttribute('href')?.includes('fonts.googleapis')
) {
return newElement;
}
insertBefore.call(head, newElement, referenceElement);
return newElement;
};
Related
Is there a TamperMonkey equivalent to GreaseMonkey's GM_addStyle method for adding CSS?
In GreaseMonkey, you can add a bunch of CSS properties to multiple elements like so:
GM_addStyle("body { color: white; background-color: black; } img { border: 0; }");
To do the equivalent in TamperMonkey, I'm currently having to do the following:
function addGlobalStyle(css) {
var head, style;
head = document.getElementsByTagName('head')[0];
if (!head) { return; }
style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = css;
head.appendChild(style);
}
addGlobalStyle('body { color: white; background-color: black; }');
This works, but is there a built-in GM_addStyle equivalent for TamperMonkey that saves me from having to repeat this on every script?
Version 4.0 or +, update of 2018
ReferenceError: GM_addStyle is not defined
You need to create your own GM_addStyle function, like this :
// ==UserScript==
// #name Example
// #description Usercript with GM_addStyle method.
// ==/UserScript==
function GM_addStyle(css) {
const style = document.getElementById("GM_addStyleBy8626") || (function() {
const style = document.createElement('style');
style.type = 'text/css';
style.id = "GM_addStyleBy8626";
document.head.appendChild(style);
return style;
})();
const sheet = style.sheet;
sheet.insertRule(css, (sheet.rules || sheet.cssRules || []).length);
}
//demo :
GM_addStyle("p { color:red; }");
GM_addStyle("p { text-decoration:underline; }");
document.body.innerHTML = "<p>I used GM_addStyle.</p><pre></pre>";
const sheet = document.getElementById("GM_addStyleBy8626").sheet,
rules = (sheet.rules || sheet.cssRules);
for (let i=0; i<rules.length; i++)
document.querySelector("pre").innerHTML += rules[i].cssText + "\n";
DEPRECATED
If GM_addStyle(...) doesn't work, check if you have #grant GM_addStyle header.
Like this :
// ==UserScript==
// #name Example
// #description See usercript with grant header.
// #grant GM_addStyle
// ==/UserScript==
GM_addStyle("body { color: white; background-color: black; } img { border: 0; }");
According to the TamperMonkey documentation, it supports GM_addStyle directly, like GreaseMonkey does. Check your include/match rules are correct, then add this demo code to the top of your userscript:
GM_addStyle('* { font-size: 99px !important; }');
console.log('ran');
I just tested it on a fresh userscript in Chrome 35 and it worked as expected. If you have any other #grant rule, you will need to add one for this function, otherwise it should be detected and granted automatically.
If somebody is interessted, I changed the code so you don't have to write "!important" after every css rule. Of course this only works, if you use the function instead of GM_addStyle.
function addGlobalStyle(css) {
var head, style;
head = document.getElementsByTagName('head')[0];
if (!head) { return; }
style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = css.replace(/;/g, ' !important;');
head.appendChild(style);
}
The output of this "addGlobalStyle('body { color: white; background-color: black; }');",
will be "body { color: white !important; background-color: black !important; }');"
I was having this same issue. I tried all the fixes, making sure to have // #grant GM_addStyle in the header. My issue was, I also had the default code's // #grant none at the bottom of the header. Removed that piece and now all my css works. Hope this helps someone else if they are stuck on this too.
I have GreaseMonkey scripts that run in various engines, this covers all varieties:
--snip--
// #include *.someplace.com/*
// #grant GM_addStyle
// ==/UserScript==
(function() {
'use strict';
let myCSS=(<><![CDATA[
body { background: #121212 url(https://somewhere.github.io/boss/imgs/bg.jpg) top left repeat !important; }
]]></>).toString();
// workaround for various GreaseMonkey engines
if (typeof GM_addStyle != "undefined") {
GM_addStyle(myCSS);
} else if (typeof PRO_addStyle != "undefined") {
PRO_addStyle(myCSS);
} else if (typeof addStyle != "undefined") {
addStyle(myCSS);
} else {
var node = document.createElement("style");
node.type = "text/css";
node.appendChild(document.createTextNode(myCSS));
var heads = document.getElementsByTagName("head");
if (heads.length > 0) {
heads[0].appendChild(node);
} else {
// no head yet, stick it whereever
document.documentElement.appendChild(node);
}
}
This excerpt from a script that was written in 2018 that is known to work in GreasMonkey, TamperMonkey, and ViolentMonkey (probably others too). Adapt the above mentioned addGlobalStyle(css) functions and you should be good to go anywhere .
My 2 cents on the topic, thought it might be interesting to someone, I modified PaarCrafter's answer, to allow multiple lines without brackets:
usage:
addGlobalStyle`
button.special {
position: relative;
top: -3em;
}
`
// string templating ('Template literals') works anyways
addGlobalStyle(`p {
color: red;
}`)
// Still works
addGlobalStyle('p {color: red;}')
Modified version:
function addGlobalStyle(css = '') {
let target = documnet.head || document.body;
let style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = (css || arguments.length ? arguments[0][0] : '').replaceAll(';', ' !important;');
target.append(style);
}
Here is the solution used by https://userstyles.org, for example when you click on the link "Install style as userscript" on a style page like https://userstyles.org/styles/23516/midnight-surfing-global-dark-style:
if (typeof GM_addStyle != "undefined") {
GM_addStyle(css);
} else if (typeof PRO_addStyle != "undefined") {
PRO_addStyle(css);
} else if (typeof addStyle != "undefined") {
addStyle(css);
} else {
var node = document.createElement("style");
node.type = "text/css";
node.appendChild(document.createTextNode(css));
var heads = document.getElementsByTagName("head");
if (heads.length > 0) {
heads[0].appendChild(node);
} else {
// no head yet, stick it whereever
document.documentElement.appendChild(node);
}
}
Note: the code will work on Greasemonkey 4 as well as similar addons. I don't use Tampermonkey which is not open source but this answer may help other users finding this question. It will try to use several built-in functions of different addons before using a pure JavaScript solution. You may only need the code from the else block.
The "if" condition checking the head tag length may not be needed if you are sure the page has a head tag and you can add the node to the head like this instead : document.head.appendChild(node);. However I noticed sometimes there is a JavaScript error saying the head is undefined or null depending on the method used, for example on facebook.com while logged out (at least when using // #run-at document-start which is required for a dark theme to avoid flickering). So checking the length can be useful in this case.
If you want to use multiple lines of CSS code, you can create the css variable with backticks like this:
var css = `
CODE HERE
`;
Update: I just saw this solution is also used in another answer but there was no source mentioned. However you may see the console error document.documentElement is null but it can be solved with a MutationObserver workaround: https://github.com/greasemonkey/greasemonkey/issues/2996#issuecomment-906608348.
I try to print the content of the details tag in Chrome but I can't force it to open.
This is what I have in my print CSS :
details, details > * { display:block !important; }
But the content appear only if I open the details before printing the page.
Is there any way to force opening details by css print on chrome ?
Reasonable cross-browser solution with jQuery (not Opera)...adapted from https://www.tjvantoll.com/2012/06/15/detecting-print-requests-with-javascript/:
// Set up before/after handlers
var beforePrint = function() {
$("details").attr('open', '');
};
var afterPrint = function() {
$("details").removeAttr('open');
};
// Webkit
if (window.matchMedia) {
var mediaQueryList = window.matchMedia('print');
mediaQueryList.addListener(function(mql) {
if (mql.matches) {
beforePrint();
} else {
afterPrint();
}
});
}
// IE, Firefox
window.onbeforeprint = beforePrint;
window.onafterprint = afterPrint;
A simple solution in vanilla JavaScript which restores the state of opened/closed details tags after printing:
// open closed details elements for printing
window.addEventListener('beforeprint',() =>
{
const allDetails = document.body.querySelectorAll('details');
for(let i=0; i<allDetails.length; i++)
{
if(allDetails[i].open)
{
allDetails[i].dataset.open = '1';
}
else
{
allDetails[i].setAttribute('open', '');
}
}
});
// after printing close details elements not opened before
window.addEventListener('afterprint',() =>
{
const allDetails = document.body.querySelectorAll('details');
for(let i=0; i<allDetails.length; i++)
{
if(allDetails[i].dataset.open)
{
allDetails[i].dataset.open = '';
}
else
{
allDetails[i].removeAttribute('open');
}
}
});
I found the solution by forcing the opening details tag with BeforePrint and Afterprint
class App.Views.main extends backbone.View
el : "body"
events :
"click [data-auto-focus]":"autoFocus"
initialize : () ->
# Add conditional classname based on support
$('html').addClass( (if $.fn.details.support then 'details' else 'no-details'))
$('details').details()
if (window.matchMedia)
mediaQueryList = window.matchMedia('print')
mediaQueryList.addListener (mql) =>
if (mql.matches)
#beforePrint()
else
#afterPrint()
window.onbeforeprint = => #beforePrint
window.onafterprint = => #afterPrint
render : () ->
openedDetailsBeforePrint : null
beforePrint : () ->
console.log "before print"
#openedDetailsBeforePrint = #$el.find('details[open], details.open')
if ($('html').hasClass('no-details')) then #$el.find('details').addClass("open") else #$el.find('details').attr("open", "")
afterPrint : () ->
console.log "after print"
#$el.find('details').removeClass(".open").removeAttr("open")
if ($('html').hasClass('no-details')) then #openedDetailsBeforePrint.addClass("open") else #openedDetailsBeforePrint.attr("open", "")
autoFocus : (e) ->
$element = if (e.currentTarget) then $(e.currentTarget) else $(e.srcElement)
return $($element.attr "data-auto-focus").focus()
Based on the answers from Christopher and Benjamin, I've created the following code:
window.matchMedia("print").addEventListener("change", evt => {
if (evt.matches) {
elms = document.body.querySelectorAll("details:not([open])");
for (e of elms) {
e.setAttribute("open", "");
e.dataset.wasclosed = "";
}
} else {
elms = document.body.querySelectorAll("details[data-wasclosed]");
for (e of elms) {
e.removeAttribute("open");
delete e.dataset.wasclosed;
}
}
})
Features:
Restores state after the print command so that details that were already open, stay open.
Minimize DOM manipulation by only altering details that are closed.
Support DevTools print emulation by using the print media query instead of the print event handler.
For this specific case (which I see today, but it takes time on StackOverflow), I think the best solution to this is add in CSS the details tag to the list of #media print, something like:
#media print {
…
details, details > * { display:block !important; }
}
A simple method in jQuery: (updated)
var mediaQueryList = window.matchMedia('print');
mediaQueryList.addListener( printTest );
function printTest(mql) {
dt = $( 'details' )
if (mql.matches) {
dt.each( function( index ){
b = $(this).attr('open');
if ( !b ){
$(this).attr('open','');
$(this).attr('print','');
}
});
} else {
dt.each( function( index ){
b = $(this).attr('print');
if ( !b ){
$(this).removeAttr('open');
$(this).removeAttr('print');
}
});
}
}
This printTest method verify if matches.
matches: open closed details elements and add an attribute print to close after.
!matches: close details elements with print attribute (and remove this attributes: open and print)
Looks more about this in JSFiddle
Here's a short answer in plain vanilla JavaScript. This just loops through all details tags, opens them all when printing, then closes them all after printing:
// Open all details tags when printing.
window.addEventListener( 'beforeprint', () => {
[].forEach.call( document.querySelectorAll( 'details' ), el => el.setAttribute( 'open', '' ) )
} )
// Close all details tags after printing.
window.addEventListener( 'afterprint', () => {
[].forEach.call( document.querySelectorAll( 'details' ), el => el.removeAttribute( 'open' ) )
} )
I have the following example:
var page = require('webpage').create(),
system = require('system');
if (system.args.length < 3) {
console.log('Usage: printheaderfooter.js URL filename');
phantom.exit(1);
} else {
var address = system.args[1];
var output = system.args[2];
page.viewportSize = { width: 600, height: 600 };
page.paperSize = {
format: 'A4',
margin: "1cm"
footer: {
height: "1cm",
contents: phantom.callback(function(pageNum, numPages) {
if (pageNum == numPages) {
return "";
}
return "<h1 class='footer_style'>Footer" + pageNum + " / " + numPages + "</h1>";
})
}
};
page.open(address, function (status) {
if (status !== 'success') {
console.log('Unable to load the address!');
} else {
window.setTimeout(function () {
page.render(output);
phantom.exit();
}, 200);
}
});
}
In the example above I use footer_style class that look likes in my css file the following:
.footer_style {
text-align:right;
}
But unfortunately that dosen't works. I'm trying to create pdf file such as follows:
./phantomjs rasterize.js index.html test.pdf
We know that classes do not work but inline styles do. What we can do is replace the class with the computed style.
Here is a function that will take a piece of html, create a temporary element in the body with the html, compute the style for each element with a class, add the computed style inline and return the new html.
function replaceClassWithStyle(html) {
return page.evaluate(function(html) {
var host = document.createElement('div');
host.innerHTML = html;
document.body.appendChild(host); // if not appended, values will be blank
var elements = host.getElementsByTagName('*');
for (var i in elements) {
if (elements[i].className) {
elements[i].setAttribute('style', window.getComputedStyle(elements[i], null).cssText);
}
}
document.body.removeChild(host);
return host.innerHTML;
}, html);
}
Then simply call this function in your footer:
page.paperSize = {
footer: {
contents: phantom.callback(function(pageNum, numPages) {
if (pageNum == numPages) {
return "";
}
return replaceClassWithStyle("<h1 class='footer_style'>Footer" + pageNum + " / " + numPages + "</h1>");
})
}
};
You will need to move all this inside page.open().
I tested it and the footer is aligned to the right.
I have an update to mak's excellent answer for PhantomJS 1.9.7.
This version fixes:
Circumvent bug which 'blank's the parent document (PhantomJS 1.9.7)
Style mixups when styles are nested (do depth-first traversal instead)
Also works when tags do not have classes
/**
* Place HTML in the parent document, convert CSS styles to fixed computed style declarations, and return HTML.
* (required for headers/footers, which exist outside of the HTML document, and have trouble getting styling otherwise)
*/
function replaceCssWithComputedStyle(html) {
return page.evaluate(function(html) {
var host = document.createElement('div');
host.setAttribute('style', 'display:none;'); // Silly hack, or PhantomJS will 'blank' the main document for some reason
host.innerHTML = html;
// Append to get styling of parent page
document.body.appendChild(host);
var elements = host.getElementsByTagName('*');
// Iterate in reverse order (depth first) so that styles do not impact eachother
for (var i = elements.length - 1; i >= 0; i--) {
elements[i].setAttribute('style', window.getComputedStyle(elements[i], null).cssText);
}
// Remove from parent page again, so we're clean
document.body.removeChild(host);
return host.innerHTML;
}, html);
}
From my past experience, phantomjs does not support styles in custom header/footer.
The only solution that I found is to apply an inline style like this :
var page = require('webpage').create(),
system = require('system');
if (system.args.length < 3) {
console.log('Usage: printheaderfooter.js URL filename');
phantom.exit(1);
} else {
var address = system.args[1];
var output = system.args[2];
page.viewportSize = { width: 600, height: 600 };
page.paperSize = {
format: 'A4',
margin: "1cm",
footer: {
height: "1cm",
contents: phantom.callback(function(pageNum, numPages) {
return "<h1 style='text-align:right'>Footer" + pageNum + " / " + numPages + "</h1>";
})
}
};
page.open(address, function (status) {
if (status !== 'success') {
console.log('Unable to load the address!');
} else {
window.setTimeout(function () {
page.render(output);
phantom.exit();
}, 200);
}
});
}
Note : A comma is missing in your code after margin: "1cm"
I would like to bind to an absolutely positioned element's top style in a directive. Is this possible?
Here is what I would like to do in made up code:
angular.module('exampleModule').directive('resize', [function () {
return {
link: function(scope, iElement, iAttrs) {
var top = 14;
// There is no styleChange event
iElement.bind('styleChange', styleChangeHandler);
function styleChangeHandler(event) {
if(event.style == 'top' && event.value != top) {
scope.$apply(function(scope){
scope[iAttrs.topChanged](event.value);
});
}
}
}
}
}]);
There are no style change events. If you are in control of the style changing you can create your custom event and trigger this manually. Or you could create a watch function, something like this:
link: function(scope, iElement, iAttrs) {
//...
scope.$watch(function(){
return iElement.css('top');
},styleChangeFn,true);
function styleChangeFn(value,old){
if(value !== old)
scope[iAttrs.topChanged](value);
}
}
So here is what I came up with (greatly helped by joakimbl's answer). It will work for watching any style.
The directive:
angular.module('unwalked.directives').directive('watchStyle', [function () {
return {
link: function(scope, iElement, iAttrs) {
scope.$watch(function(){
return iElement.css(iAttrs['watchedStyle']);
},
styleChanged,
true);
function styleChanged(newValue, oldValue) {
if(newValue !== oldValue) {
scope[iAttrs['watchStyle']](newValue);
}
}
}
};
}]);
Usage (Note: no brackets on the callback - it's just the function name):
<div watch-style="functionOnController" watched-style="height" >
When I create an input box in topbar using bootstrap css, the placeholder value is visible in all browsers except IE. But in twitter site they're making it visible somehow(not javascript, cause I turned off scripts and tested). Can anyone out there help me ?
I was having similar issues and faked the same placeholder functionality for IE by doing this.
<script type="text/javascript">
$(function() {
if(!$.support.placeholder) {
var active = document.activeElement;
$(':text').focus(function () {
if ($(this).attr('placeholder') != '' && $(this).val() == $(this).attr('placeholder')) {
$(this).val('').removeClass('hasPlaceholder');
}
}).blur(function () {
if ($(this).attr('placeholder') != '' && ($(this).val() == '' || $(this).val() == $(this).attr('placeholder'))) {
$(this).val($(this).attr('placeholder')).addClass('hasPlaceholder');
}
});
$(':text').blur();
$(active).focus();
$('form').submit(function () {
$(this).find('.hasPlaceholder').each(function() { $(this).val(''); });
});
}
});
</script>
Source: http://www.cssnewbie.com/cross-browser-support-for-html5-placeholder-text-in-forms/
Would be good to see your code, but I'll assume you set the placeholder="my placeholder" attribute on the input tag. This is new HTML5 functionality, so that's why it is working without JavaScript.
There's a few reasons I can think of why it's not working.
Set your doctype correctly to html, which indicates html5.
Make sure there's nothing before the doctype declaration, not even empty lines.
Set your css-styles correctly. You'll have to use vendor prefixed
since it's a relatively new feature:
::-webkit-input-placeholder { color:#999; };
:-moz-placeholder { color:#999; };
:-ms-input-placeholder { color:#999; };
:placeholder { color:#999; };
.placeholder { color:#999; };